-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
8,916 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"projects": { | ||
"default": "mobility-feeds-dev", | ||
"dev": "mobility-feeds-dev", | ||
"qa": "mobility-feeds-qa", | ||
"prod": "mobility-feeds-prod" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Firebase Typescript Functions | ||
|
||
## Overview | ||
This project utilizes [Firebase Functions](https://firebase.google.com/docs/functions) within a monorepo architecture managed by [`yarn` workspaces](https://classic.yarnpkg.com/lang/en/docs/workspaces/). It employs Firebase for deploying serverless functions. | ||
|
||
## Project Structure | ||
- `functions` directory: Contains the Firebase Functions. | ||
- `packages` directory: Contains the function-specific code. | ||
- `firebase.json`: Configuration file for Firebase services. | ||
- `package.json`: Defines workspaces and scripts for managing the monorepo. | ||
|
||
## Development | ||
### Adding New Functions | ||
1. **Create Function**: In the `packages/` directory, create a new directory for your function (e.g., `packages/my-new-function`). | ||
2. **Setup Function**: Inside your function directory, initialize it with `yarn init` and set up your function code. | ||
3. **Register Function**: Update `firebase.json` to include your new function. Add a new entry under `functions` with appropriate `source` and `codebase` values. | ||
4. **Dependencies**: Manage any specific dependencies for your function within its directory. | ||
### Local Development | ||
1. **Build**: Run `yarn build` to build all workspaces. | ||
2. **Emulate Functions**: Use `firebase emulators:start` to test functions locally. | ||
|
||
## Deployment | ||
### Build All Functions | ||
Run `yarn build` to build all the functions. | ||
|
||
### Deploy All Functions | ||
To deploy the functions to Firebase use: | ||
```shell | ||
firebase deploy --only functions | ||
``` | ||
|
||
### Deploy Functions Within a Codebase | ||
To deploy a specific function to Firebase use: | ||
```shell | ||
firebase deploy --only functions:<codebase> | ||
``` | ||
|
||
### Delete Functions | ||
To delete a function from Firebase use: | ||
```shell | ||
firebase functions:delete <function_name> | ||
``` | ||
|
||
## Testing | ||
Run `yarn test` to execute tests across all workspaces. | ||
|
||
## Linting | ||
Run `yarn lint` to run linters across all workspaces. | ||
|
||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"functions": [ | ||
{ | ||
"source": "packages/user-api", | ||
"codebase": "user-api", | ||
"ignore": [ | ||
"node_modules", | ||
".git", | ||
"__tests__", | ||
"firebase-debug.log", | ||
"firebase-debug.*.log" | ||
], | ||
"predeploy": [ | ||
"yarn --cwd \"$RESOURCE_DIR\" install", | ||
"yarn --cwd \"$RESOURCE_DIR\" build" | ||
] | ||
} | ||
], | ||
"emulators": { | ||
"functions": { | ||
"port": 5030 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"private": true, | ||
"workspaces": ["packages/*"], | ||
"scripts": { | ||
"test": "yarn workspaces run test", | ||
"build": "yarn workspaces run build", | ||
"lint": "yarn workspaces run lint" | ||
}, | ||
"devDependencies": { | ||
"firebase-tools": "^12.5.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
module.exports = { | ||
root: true, | ||
env: { | ||
es6: true, | ||
node: true, | ||
}, | ||
extends: [ | ||
"eslint:recommended", | ||
"plugin:import/errors", | ||
"plugin:import/warnings", | ||
"plugin:import/typescript", | ||
"google", | ||
"plugin:@typescript-eslint/recommended", | ||
], | ||
parser: "@typescript-eslint/parser", | ||
parserOptions: { | ||
project: ["tsconfig.json", "tsconfig.dev.json"], | ||
sourceType: "module", | ||
}, | ||
ignorePatterns: [ | ||
"/lib/**/*", // Ignore built files. | ||
"**/*config.*", // Ignore config files. | ||
], | ||
plugins: [ | ||
"@typescript-eslint", | ||
"import", | ||
], | ||
rules: { | ||
"quotes": ["error", "double"], | ||
"import/no-unresolved": 0, | ||
"indent": ["error", 2], | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# This file specifies files that are *not* uploaded to Google Cloud | ||
# using gcloud. It follows the same syntax as .gitignore, with the addition of | ||
# "#!include" directives (which insert the entries of the given .gitignore-style | ||
# file at that point). | ||
# | ||
# For more information, run: | ||
# $ gcloud topic gcloudignore | ||
# | ||
.gcloudignore | ||
# If you would like to upload your .git directory, .gitignore file or files | ||
# from your .gitignore file, remove the corresponding line | ||
# below: | ||
.git | ||
.gitignore | ||
|
||
node_modules | ||
#!include:.gitignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Compiled JavaScript files | ||
lib/**/*.js | ||
lib/**/*.js.map | ||
|
||
# TypeScript v1 declaration files | ||
typings/ | ||
|
||
# Node.js dependency directory | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"name": "user-api", | ||
"version": "1.0.0", | ||
"scripts": { | ||
"lint": "eslint --ext .js,.ts .", | ||
"build": "tsc", | ||
"build:watch": "tsc --watch", | ||
"serve": "yarn build && firebase emulators:start --only functions", | ||
"shell": "yarn build && firebase functions:shell", | ||
"start": "yarn shell", | ||
"test": "jest", | ||
"deploy": "firebase deploy --only functions", | ||
"logs": "firebase functions:log" | ||
}, | ||
"engines": { | ||
"node": "18" | ||
}, | ||
"main": "lib/index.js", | ||
"dependencies": { | ||
"@google-cloud/datastore": "^8.2.2", | ||
"firebase": "^10.6.0", | ||
"firebase-admin": "^11.8.0", | ||
"firebase-functions": "^4.3.1" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^29.5.8", | ||
"@typescript-eslint/eslint-plugin": "^5.12.0", | ||
"@typescript-eslint/parser": "^5.12.0", | ||
"eslint": "^8.9.0", | ||
"eslint-config-google": "^0.14.0", | ||
"eslint-plugin-import": "^2.25.4", | ||
"firebase-functions-test": "^3.1.0", | ||
"jest": "^29.7.0", | ||
"ts-jest": "^29.1.1", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.0" | ||
}, | ||
"private": true | ||
} |
122 changes: 122 additions & 0 deletions
122
functions/packages/user-api/src/__tests__/user-api.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { | ||
retrieveUser, | ||
updateUserInformation, | ||
retrieveUserInformation} | ||
from "../impl/user-api-impl"; | ||
import {Datastore} from "@google-cloud/datastore"; | ||
import {CallableRequest, HttpsError} from "firebase-functions/v2/https"; | ||
|
||
jest.mock("@google-cloud/datastore"); | ||
|
||
describe("retrieveUser", () => { | ||
it("should retrieve a user by uid", async () => { | ||
const mockUid = "testUid"; | ||
const mockUser = { | ||
uid: mockUid, | ||
fullName: "Test User", | ||
organization: "Test Org", | ||
}; | ||
|
||
// mocking the Datastore query | ||
Datastore.prototype.createQuery = jest.fn().mockReturnValue({ | ||
filter: jest.fn().mockReturnThis(), | ||
limit: jest.fn().mockReturnThis(), | ||
run: jest.fn().mockResolvedValue([[mockUser]]), | ||
}); | ||
const mockDatastore = new Datastore(); | ||
|
||
const result = await retrieveUser(mockDatastore, mockUid); | ||
expect(result).toEqual(mockUser); | ||
}); | ||
}); | ||
|
||
describe("retrieveUserInformation", () => { | ||
it("should throw an error if uid is not provided", async () => { | ||
const mockRequest = { | ||
auth: {uid: undefined}, | ||
rawRequest: {}, | ||
}; | ||
await expect( | ||
retrieveUserInformation(mockRequest as unknown as CallableRequest)) | ||
.rejects | ||
.toThrow(HttpsError); | ||
}); | ||
}); | ||
|
||
describe("updateUserInformation", () => { | ||
beforeEach(() => { | ||
jest.mock("../impl/user-api-impl.ts", () => ({ | ||
retrieveUserKey: jest.fn().mockResolvedValue("userKey"), | ||
})); | ||
Datastore.prototype.save = jest.fn().mockReturnValue({ | ||
catch: jest.fn().mockReturnThis(), | ||
}); | ||
}); | ||
|
||
it("should update user information", async () => { | ||
const mockRequest = { | ||
auth: {uid: "testUid"}, | ||
data: {fullName: "Test User", organization: "Test Org"}, | ||
rawRequest: {}, | ||
}; | ||
const result = await updateUserInformation(mockRequest as CallableRequest); | ||
expect(result).toEqual("User testUid updated successfully."); | ||
}); | ||
|
||
it("should update user information if organization is undefined", | ||
async () => { | ||
const mockRequest = { | ||
auth: {uid: "testUid"}, | ||
data: {fullName: "Test User", organization: undefined}, | ||
rawRequest: {}, | ||
}; | ||
const result = | ||
await updateUserInformation(mockRequest as CallableRequest); | ||
expect(result).toEqual("User testUid updated successfully."); | ||
}); | ||
|
||
it("should throw an error if uid is not provided", async () => { | ||
const mockRequest = { | ||
auth: {uid: undefined}, | ||
data: {fullName: "Test User", organization: "Test Org"}, | ||
rawRequest: {}, | ||
}; | ||
await expect( | ||
updateUserInformation(mockRequest as unknown as CallableRequest)) | ||
.rejects | ||
.toThrow(HttpsError); | ||
}); | ||
|
||
it("should throw an error if fullName is not provided", async () => { | ||
const mockRequest = { | ||
auth: {uid: "testUid"}, | ||
data: {fullName: undefined, organization: "Test Org"}, | ||
rawRequest: {}, | ||
}; | ||
await expect( | ||
updateUserInformation(mockRequest as unknown as CallableRequest)) | ||
.rejects | ||
.toThrow(HttpsError); | ||
}); | ||
it("should throw an HttpsError when save throws an error", async () => { | ||
const mockRequest = { | ||
auth: {uid: "testUid"}, | ||
data: {fullName: "Test User", organization: "Test Org"}, | ||
rawRequest: {}, | ||
}; | ||
|
||
Datastore.prototype.save = jest.fn().mockImplementation(() => { | ||
throw new Error("Datastore save error"); | ||
}); | ||
|
||
await expect( | ||
updateUserInformation(mockRequest as unknown as CallableRequest)) | ||
.rejects | ||
.toThrow(HttpsError); | ||
|
||
await expect( | ||
updateUserInformation(mockRequest as unknown as CallableRequest)) | ||
.rejects | ||
.toThrow(new HttpsError("internal", "Unable to update user information")); | ||
}); | ||
}); |
Oops, something went wrong.