From 2e4ab40abf2122340a8ecc3b80e7ef1dfe86ab8f Mon Sep 17 00:00:00 2001 From: Popotojs Date: Mon, 10 Jan 2022 22:38:44 +0100 Subject: [PATCH] add first version of code with unit tests --- README.md | 11 +- firebase.json | 2 +- lib/firebase.constants.ts | 5 +- lib/firebase.module.ts | 25 +-- lib/interface/firebase-config.ts | 5 +- lib/interface/index.ts | 5 +- lib/service/auth.service.ts | 40 +++++ lib/service/firestore.service.ts | 61 +++++-- lib/service/storage.service.ts | 30 ++-- test/e2e/auth.async.ts | 55 +++++++ test/e2e/auth.spec.ts | 69 ++++++++ test/e2e/firebase.spec.ts | 39 ----- test/e2e/firestore.async.spec.ts | 73 +++++++++ test/e2e/firestore.spec.ts | 225 ++++++++++++++++++++++++++ test/src/.test.env | 3 + test/src/app.async.module.ts | 22 +++ test/src/app.module.ts | 15 ++ test/src/cats/cats.controller.ts | 19 +++ test/src/cats/cats.module.ts | 11 ++ test/src/cats/cats.service.ts | 36 +++++ test/src/cats/dto/create-cat.dto.ts | 5 + test/src/cats/dto/update-cat.dto.ts | 6 + test/src/cats/models/cat.converter.ts | 39 +++++ test/src/cats/models/cat.model.ts | 6 + 24 files changed, 717 insertions(+), 90 deletions(-) create mode 100644 lib/service/auth.service.ts create mode 100644 test/e2e/auth.async.ts create mode 100644 test/e2e/auth.spec.ts delete mode 100644 test/e2e/firebase.spec.ts create mode 100644 test/e2e/firestore.async.spec.ts create mode 100644 test/e2e/firestore.spec.ts create mode 100644 test/src/.test.env create mode 100644 test/src/app.async.module.ts create mode 100644 test/src/app.module.ts create mode 100644 test/src/cats/cats.controller.ts create mode 100644 test/src/cats/cats.module.ts create mode 100644 test/src/cats/cats.service.ts create mode 100644 test/src/cats/dto/create-cat.dto.ts create mode 100644 test/src/cats/dto/update-cat.dto.ts create mode 100644 test/src/cats/models/cat.converter.ts create mode 100644 test/src/cats/models/cat.model.ts diff --git a/README.md b/README.md index 32707de..a3f389f 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,21 @@ # @nhogs/nestjs-firebase -[Firebase](https://firebase.google.com/) module for [NestJS](https://github.com/nestjs/nest). ---------------------------- +## [Firebase](https://firebase.google.com/) module for [NestJS](https://github.com/nestjs/nest). + [![npm peer dependency version Firebase](https://img.shields.io/npm/dependency-version/@nhogs/nestjs-firebase/peer/firebase?label=Firebase&logo=firebase)](https://firebase.google.com/) [![npm peer dependency version NestJS)](https://img.shields.io/npm/dependency-version/@nhogs/nestjs-firebase/peer/@nestjs/core?label=Nestjs&logo=nestjs&logoColor=e0234e)](https://github.com/nestjs/nest) ---- + [![CI](https://github.com/nhogs/nestjs-firebase/actions/workflows/ci.yml/badge.svg)](https://github.com/Nhogs/nestjs-firebase/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/Nhogs/nestjs-firebase/branch/main/graph/badge.svg?token=ZRPM5WFGO2)](https://codecov.io/gh/Nhogs/nestjs-firebase) [![Maintainability](https://api.codeclimate.com/v1/badges/356bd937ca8b2e7b8d96/maintainability)](https://codeclimate.com/github/Nhogs/nestjs-firebase/maintainability) ## Installation + [![npm](https://img.shields.io/npm/v/@nhogs/nestjs-firebase?label=%40nhogs%2Fnestjs-firebase&logo=npm)](https://www.npmjs.com/package/@nhogs/nestjs-firebase) ```bash $ npm i --save @nhogs/nestjs-firebase ``` - ## License - -[MIT license](LICENSE). +[![MIT license](https://img.shields.io/github/license/nhogs/nestjs-firebase)](LICENSE) diff --git a/firebase.json b/firebase.json index 9e400ca..a8537d1 100644 --- a/firebase.json +++ b/firebase.json @@ -7,7 +7,7 @@ "port": 9199 }, "ui": { - "enabled": false + "enabled": true }, "auth": { "port": 9099 diff --git a/lib/firebase.constants.ts b/lib/firebase.constants.ts index a06b6c5..223cda9 100644 --- a/lib/firebase.constants.ts +++ b/lib/firebase.constants.ts @@ -1,2 +1,3 @@ -export const FIREBASE_CONFIG: string = 'FirebaseConfig'; -export const FIREBASE_APP: string = 'FirebaseApp'; +export const FIREBASE_CONFIG: string = "FIREBASE_CONFIG"; +export const FIREBASE_APP: string = "FIREBASE_APP"; +export const DEFAULT_APP_NAME: string = "DEFAULT_APP_NAME"; diff --git a/lib/firebase.module.ts b/lib/firebase.module.ts index 789627e..75258e5 100644 --- a/lib/firebase.module.ts +++ b/lib/firebase.module.ts @@ -3,13 +3,14 @@ import { Module, OnApplicationShutdown, Provider, -} from '@nestjs/common'; -import { FirestoreService } from './service'; -import { ConfigModule } from '@nestjs/config'; -import { initializeApp } from 'firebase/app'; -import { StorageService } from './service'; -import { FIREBASE_APP, FIREBASE_CONFIG } from './firebase.constants'; -import {FirebaseConfig} from "./interface"; +} from "@nestjs/common"; +import { FirestoreService } from "./service"; +import { ConfigModule } from "@nestjs/config"; +import { initializeApp } from "firebase/app"; +import { StorageService } from "./service"; +import { FIREBASE_APP, FIREBASE_CONFIG } from "./firebase.constants"; +import { FirebaseConfig } from "./interface"; +import { AuthService } from "./service/auth.service"; export const createApp = async (config: FirebaseConfig) => { return initializeApp(config); @@ -17,15 +18,14 @@ export const createApp = async (config: FirebaseConfig) => { @Module({}) export class FirebaseModule implements OnApplicationShutdown { - async onApplicationShutdown() { - - } + async onApplicationShutdown() {} static forRoot(config: FirebaseConfig): DynamicModule { return { module: FirebaseModule, global: true, providers: [ + AuthService, FirestoreService, StorageService, { @@ -38,7 +38,7 @@ export class FirebaseModule implements OnApplicationShutdown { useFactory: async (config: FirebaseConfig) => createApp(config), }, ], - exports: [FirestoreService, StorageService], + exports: [AuthService, FirestoreService, StorageService], }; } @@ -49,6 +49,7 @@ export class FirebaseModule implements OnApplicationShutdown { imports: [ConfigModule], providers: [ + AuthService, FirestoreService, StorageService, { @@ -62,7 +63,7 @@ export class FirebaseModule implements OnApplicationShutdown { }, ], - exports: [FirestoreService, StorageService], + exports: [AuthService, FirestoreService, StorageService], }; } } diff --git a/lib/interface/firebase-config.ts b/lib/interface/firebase-config.ts index 754da6e..4f2cbc4 100644 --- a/lib/interface/firebase-config.ts +++ b/lib/interface/firebase-config.ts @@ -1,5 +1,6 @@ export interface FirebaseConfig { - apiKey: string; - authDomain: string; projectId: string; + apiKey?: string; + authDomain?: string; + emulator?: boolean; } diff --git a/lib/interface/index.ts b/lib/interface/index.ts index 5eb747c..ddcbe07 100644 --- a/lib/interface/index.ts +++ b/lib/interface/index.ts @@ -1 +1,4 @@ -export * from './firebase-config'; +import { FirestoreDataConverter } from "@firebase/firestore"; + +export * from "./firebase-config"; +export { FirestoreDataConverter }; diff --git a/lib/service/auth.service.ts b/lib/service/auth.service.ts new file mode 100644 index 0000000..0c283e4 --- /dev/null +++ b/lib/service/auth.service.ts @@ -0,0 +1,40 @@ +import { Inject, Injectable, Logger } from "@nestjs/common"; +import { FirebaseApp } from "firebase/app"; +import { FIREBASE_APP, FIREBASE_CONFIG } from "../firebase.constants"; +import { + connectAuthEmulator, + createUserWithEmailAndPassword, + getAuth, + signInWithEmailAndPassword, + UserCredential, +} from "firebase/auth"; +import { FirebaseConfig } from "../interface"; + +@Injectable() +export class AuthService { + private readonly _logger = new Logger(AuthService.name); + private readonly _auth; + constructor( + @Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp, + @Inject(FIREBASE_CONFIG) private readonly firebaseConfig: FirebaseConfig + ) { + this._auth = getAuth(this.firebaseApp); + if (this.firebaseConfig.emulator) { + connectAuthEmulator(this._auth, "http://localhost:9099"); + } + } + + async createUserWithEmailAndPassword( + email: string, + password: string + ): Promise { + return createUserWithEmailAndPassword(this._auth, email, password); + } + + async signInWithEmailAndPassword( + email: string, + password: string + ): Promise { + return signInWithEmailAndPassword(this._auth, email, password); + } +} diff --git a/lib/service/firestore.service.ts b/lib/service/firestore.service.ts index 540dbf0..1a5cd92 100644 --- a/lib/service/firestore.service.ts +++ b/lib/service/firestore.service.ts @@ -1,32 +1,40 @@ import { Inject, Injectable, Logger } from "@nestjs/common"; import { FirebaseApp } from "@firebase/app"; import { + addDoc, collection as firebaseCollection, CollectionReference, + connectFirestoreEmulator, doc, FirestoreDataConverter, getDoc, getDocs, getFirestore, + setDoc, } from "@firebase/firestore"; -import { FIREBASE_APP } from "../firebase.constants"; +import { FIREBASE_APP, FIREBASE_CONFIG } from "../firebase.constants"; +import { FirebaseConfig } from "../interface"; @Injectable() export class FirestoreService { - constructor( - @Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp - ) {} private readonly _logger = new Logger(FirestoreService.name); - private db = getFirestore(this.firebaseApp); + private readonly _db; + constructor( + @Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp, + @Inject(FIREBASE_CONFIG) private readonly firebaseConfig: FirebaseConfig + ) { + this._db = getFirestore(this.firebaseApp); + if (this.firebaseConfig.emulator) { + connectFirestoreEmulator(this._db, "localhost", 8080); + } + } async getDocs( collection: string, converter: FirestoreDataConverter ): Promise { - this._logger.debug("getDocs(" + collection + ")"); - const collectionRef: CollectionReference = firebaseCollection( - this.db, + this._db, collection ).withConverter(converter); @@ -47,17 +55,38 @@ export class FirestoreService { id: string, converter: FirestoreDataConverter ): Promise { - this._logger.debug( - "getDoc(" + JSON.stringify({ collection, id, converter }) + ")" - ); - - const docRef = doc(this.db, collection, id).withConverter(converter); - const documentSnapshot = await getDoc(docRef); + const ref = doc(this._db, collection, id).withConverter(converter); + const snapshot = await getDoc(ref); - if (documentSnapshot.exists()) { - return documentSnapshot.data(); + if (snapshot.exists()) { + return snapshot.data(); } else { return null; } } + + async setDoc( + path, + id: string, + data: T, + converter?: FirestoreDataConverter + ): Promise { + const ref = doc(this._db, path, id).withConverter(converter); + await setDoc(ref, data); + const snapshot = await getDoc(ref); + + return snapshot.data(); + } + + async addDoc( + path, + data: T, + converter?: FirestoreDataConverter + ): Promise { + const dataDocRef = await addDoc(firebaseCollection(this._db, path), data); + const ref = dataDocRef.withConverter(converter); + const snapshot = await getDoc(ref); + + return snapshot.data(); + } } diff --git a/lib/service/storage.service.ts b/lib/service/storage.service.ts index 0abba2c..f77d427 100644 --- a/lib/service/storage.service.ts +++ b/lib/service/storage.service.ts @@ -1,18 +1,26 @@ -import { Inject, Injectable, Logger } from '@nestjs/common'; -import { FirebaseApp } from '@firebase/app'; -import { getDownloadURL, getStorage, ref } from '@firebase/storage'; -import { FIREBASE_APP } from '../firebase.constants'; +import {Inject, Injectable, Logger} from "@nestjs/common"; +import {FirebaseApp} from "@firebase/app"; +import {connectStorageEmulator, getStorage,} from "@firebase/storage"; +import {FIREBASE_APP, FIREBASE_CONFIG} from "../firebase.constants"; +import {FirebaseConfig} from "../interface"; @Injectable() export class StorageService { - constructor( - @Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp, - ) {} private readonly _logger = new Logger(StorageService.name); - private storage = getStorage(this.firebaseApp); + private readonly _storage; - async getDownloadURL(url: string): Promise { - this._logger.debug('getDownloadURL=>' + url); - return getDownloadURL(ref(this.storage, url)); + constructor( + @Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp, + @Inject(FIREBASE_CONFIG) private readonly firebaseConfig: FirebaseConfig + ) { + this._storage = getStorage(this.firebaseApp); + if (this.firebaseConfig.emulator) { + connectStorageEmulator(this._storage, "localhost", 9199); + } } + // + // async getDownloadURL(url: string): Promise { + // this._logger.debug('getDownloadURL=>' + url); + // return getDownloadURL(ref(this.storage, url)); + // } } diff --git a/test/e2e/auth.async.ts b/test/e2e/auth.async.ts new file mode 100644 index 0000000..15e1577 --- /dev/null +++ b/test/e2e/auth.async.ts @@ -0,0 +1,55 @@ +import { INestApplication } from "@nestjs/common"; +import { Test, TestingModule } from "@nestjs/testing"; +import axios from "axios"; +import { AppAsyncModule } from "../src/app.async.module"; +import { AuthService } from "../../lib/service/auth.service"; + +describe("Firebase", () => { + let module: TestingModule; + let app: INestApplication; + let authService: AuthService; + + beforeAll(async () => { + module = await Test.createTestingModule({ + imports: [AppAsyncModule], + }).compile(); + + app = module.createNestApplication(); + await app.init(); + authService = module.get(AuthService); + }); + + it(`should create and sign in user`, async () => { + const user = ( + await authService.createUserWithEmailAndPassword( + "user.email@gmail.com", + "userpassword" + ) + ).user; + + expect(user.email).toMatchInlineSnapshot(`"user.email@gmail.com"`); + + const user2 = ( + await authService.signInWithEmailAndPassword( + "user.email@gmail.com", + "userpassword" + ) + ).user; + + expect(user2.email).toMatchInlineSnapshot(`"user.email@gmail.com"`); + }); + + afterEach(async () => { + return await axios + .delete( + "http://localhost:9099/emulator/v1/projects/demo-nhogs-nestjs-firebase/accounts" + ) + .then(function (response) { + expect(response.status).toMatchInlineSnapshot(`200`); + }); + }); + + afterAll(async () => { + return await app.close(); + }); +}); diff --git a/test/e2e/auth.spec.ts b/test/e2e/auth.spec.ts new file mode 100644 index 0000000..c07262a --- /dev/null +++ b/test/e2e/auth.spec.ts @@ -0,0 +1,69 @@ +import { INestApplication } from "@nestjs/common"; +import { Test } from "@nestjs/testing"; +import { Server } from "http"; +import { AppModule } from "../src/app.module"; +import axios from "axios"; +import { AuthService } from "../../lib/service/auth.service"; + +describe("Firebase", () => { + let server: Server; + let app: INestApplication; + let authService: AuthService; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + authService = module.get(AuthService); + }); + it(`should fail sign in user`, async () => { + try { + await authService.signInWithEmailAndPassword( + "user.email@gmail.com", + "userpassword" + ); + } catch (error) { + expect(error).toMatchInlineSnapshot( + `[FirebaseError: Firebase: Error (auth/user-not-found).]` + ); + } + }); + + it(`should create and sign in user`, async () => { + const user = ( + await authService.createUserWithEmailAndPassword( + "user.email@gmail.com", + "userpassword" + ) + ).user; + + expect(user.email).toMatchInlineSnapshot(`"user.email@gmail.com"`); + + const user2 = ( + await authService.signInWithEmailAndPassword( + "user.email@gmail.com", + "userpassword" + ) + ).user; + + expect(user2.email).toMatchInlineSnapshot(`"user.email@gmail.com"`); + }); + + afterEach(async () => { + return await axios + .delete( + "http://localhost:9099/emulator/v1/projects/demo-nhogs-nestjs-firebase/accounts" + ) + .then(function (response) { + expect(response.status).toMatchInlineSnapshot(`200`); + }); + }); + + afterAll(async () => { + return await app.close(); + }); +}); diff --git a/test/e2e/firebase.spec.ts b/test/e2e/firebase.spec.ts deleted file mode 100644 index 53fc604..0000000 --- a/test/e2e/firebase.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { INestApplication } from "@nestjs/common"; -import { Test } from "@nestjs/testing"; -import { Server } from "http"; -import { FirebaseModule } from "../../lib/firebase.module"; -import { ConfigModule, ConfigService } from "@nestjs/config"; -import { FirebaseConfig } from "../../lib"; - -describe("Firebase", () => { - let server: Server; - let app: INestApplication; - - beforeEach(async () => { - const module = await Test.createTestingModule({ - imports: [ - FirebaseModule.forRootAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (configService: ConfigService): FirebaseConfig => ({ - apiKey: configService.get("FIREBASE_API_KEY"), - authDomain: configService.get("FIREBASE_AUTH_DOMAIN"), - projectId: configService.get("FIREBASE_PROJECT_ID"), - }), - }), - ], - }).compile(); - - app = module.createNestApplication(); - server = app.getHttpServer(); - await app.init(); - }); - - it(`should`, () => { - expect(true); - }); - - afterEach(async () => { - await app.close(); - }); -}); diff --git a/test/e2e/firestore.async.spec.ts b/test/e2e/firestore.async.spec.ts new file mode 100644 index 0000000..b200fa3 --- /dev/null +++ b/test/e2e/firestore.async.spec.ts @@ -0,0 +1,73 @@ +import { INestApplication } from "@nestjs/common"; +import { Test } from "@nestjs/testing"; +import { Server } from "http"; +import { CatsService } from "../src/cats/cats.service"; +import axios from "axios"; +import { AppAsyncModule } from "../src/app.async.module"; + +describe("Firebase", () => { + let server: Server; + let app: INestApplication; + let catsService: CatsService; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [AppAsyncModule], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + catsService = module.get(CatsService); + }); + + it(`should create and find one by id`, async () => { + let cat = await catsService.create({ + name: "Nest", + breed: "Maine coon", + age: 5, + }); + + expect(cat).toMatchInlineSnapshot( + { + id: expect.any(String), + }, + ` + Object { + "age": 5, + "breed": "Maine coon", + "id": Any, + "name": "Nest", + } + ` + ); + + expect(await catsService.findOneById(cat.id)).toMatchInlineSnapshot( + { + id: expect.any(String), + }, + ` + Object { + "age": 5, + "breed": "Maine coon", + "id": Any, + "name": "Nest", + } + ` + ); + }); + + afterEach(async () => { + return await axios + .delete( + "http://localhost:8080/emulator/v1/projects/demo-nhogs-nestjs-firebase/databases/(default)/documents" + ) + .then(function (response) { + expect(response.status).toMatchInlineSnapshot(`200`); + }); + }); + + afterAll(async () => { + return await app.close(); + }); +}); diff --git a/test/e2e/firestore.spec.ts b/test/e2e/firestore.spec.ts new file mode 100644 index 0000000..477677f --- /dev/null +++ b/test/e2e/firestore.spec.ts @@ -0,0 +1,225 @@ +import { INestApplication } from "@nestjs/common"; +import { Test } from "@nestjs/testing"; +import { Server } from "http"; +import { CatsService } from "../src/cats/cats.service"; +import { AppModule } from "../src/app.module"; +import axios from "axios"; + +describe("Firestore", () => { + let server: Server; + let app: INestApplication; + let catsService: CatsService; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + catsService = module.get(CatsService); + }); + + it(`should create and find one by id`, async () => { + let cat = await catsService.create({ + name: "Nest", + breed: "Maine coon", + age: 5, + }); + + expect(cat).toMatchInlineSnapshot( + { + id: expect.any(String), + }, + ` + Object { + "age": 5, + "breed": "Maine coon", + "id": Any, + "name": "Nest", + } + ` + ); + + expect(await catsService.findOneById(cat.id)).toMatchInlineSnapshot( + { + id: expect.any(String), + }, + ` + Object { + "age": 5, + "breed": "Maine coon", + "id": Any, + "name": "Nest", + } + ` + ); + }); + + it(`should not find unknown id`, async () => { + expect(await catsService.findOneById("UNKNOWN")).toMatchInlineSnapshot( + `null` + ); + }); + + it(`should create and find all`, async () => { + await catsService.create({ + name: "Snowflake", + breed: "Maine coon", + age: 3, + }); + await catsService.create({ + name: "Jellybean", + breed: "Birman", + age: 1, + }); + await catsService.create({ + name: "Marshmallow", + breed: "Bengal", + age: 4, + }); + await catsService.create({ + name: "Minnie", + breed: "Scottish Fold", + age: 2, + }); + await catsService.create({ + name: "Puffin", + breed: "Siamese", + age: 8, + }); + + expect({ + cats: (await catsService.findAll()).sort((a, b) => + b.name.localeCompare(a.name) + ), + }).toMatchInlineSnapshot( + { + cats: [ + { + id: expect.any(String), + }, + + { + id: expect.any(String), + }, + + { + id: expect.any(String), + }, + + { + id: expect.any(String), + }, + + { + id: expect.any(String), + }, + ], + }, + ` + Object { + "cats": Array [ + Object { + "age": 3, + "breed": "Maine coon", + "id": Any, + "name": "Snowflake", + }, + Object { + "age": 8, + "breed": "Siamese", + "id": Any, + "name": "Puffin", + }, + Object { + "age": 2, + "breed": "Scottish Fold", + "id": Any, + "name": "Minnie", + }, + Object { + "age": 4, + "breed": "Bengal", + "id": Any, + "name": "Marshmallow", + }, + Object { + "age": 1, + "breed": "Birman", + "id": Any, + "name": "Jellybean", + }, + ], + } + ` + ); + }); + + it(`should set doc`, async () => { + let cat = await catsService.update({ + id: "myId", + name: "Nest", + breed: "Maine coon", + age: 5, + }); + + expect(cat).toMatchInlineSnapshot(` + Object { + "age": 5, + "breed": "Maine coon", + "id": "myId", + "name": "Nest", + } + `); + + expect(await catsService.findOneById("myId")).toMatchInlineSnapshot(` + Object { + "age": 5, + "breed": "Maine coon", + "id": "myId", + "name": "Nest", + } + `); + + cat = await catsService.update({ + id: "myId", + name: "Snowflake", + breed: "Maine coon", + age: 5, + }); + + expect(cat).toMatchInlineSnapshot(` + Object { + "age": 5, + "breed": "Maine coon", + "id": "myId", + "name": "Snowflake", + } + `); + + expect(await catsService.findOneById("myId")).toMatchInlineSnapshot(` + Object { + "age": 5, + "breed": "Maine coon", + "id": "myId", + "name": "Snowflake", + } + `); + }); + + afterEach(async () => { + return await axios + .delete( + "http://localhost:8080/emulator/v1/projects/demo-nhogs-nestjs-firebase/databases/(default)/documents" + ) + .then(function (response) { + expect(response.status).toMatchInlineSnapshot(`200`); + }); + }); + + afterAll(async () => { + return await app.close(); + }); +}); diff --git a/test/src/.test.env b/test/src/.test.env new file mode 100644 index 0000000..2659e75 --- /dev/null +++ b/test/src/.test.env @@ -0,0 +1,3 @@ +FIREBASE_APIKEY=API-KEY-FOR-TEST +FIREBASE_PROJECT_ID=demo-nhogs-nestjs-firebase +FIREBASE_EMULATOR=true diff --git a/test/src/app.async.module.ts b/test/src/app.async.module.ts new file mode 100644 index 0000000..47e4cb4 --- /dev/null +++ b/test/src/app.async.module.ts @@ -0,0 +1,22 @@ +import { Module } from "@nestjs/common"; +import { FirebaseConfig, FirebaseModule } from "../../lib"; +import { ConfigModule } from "@nestjs/config"; +import { CatsModule } from "./cats/cats.module"; + +@Module({ + imports: [ + FirebaseModule.forRootAsync({ + useFactory: (): FirebaseConfig => ({ + apiKey: process.env.FIREBASE_APIKEY, + projectId: process.env.FIREBASE_PROJECT_ID, + emulator: process.env.FIREBASE_EMULATOR === "true", + }), + global: true, + }), + CatsModule, + ConfigModule.forRoot({ + envFilePath: "./test/src/.test.env", + }), + ], +}) +export class AppAsyncModule {} diff --git a/test/src/app.module.ts b/test/src/app.module.ts new file mode 100644 index 0000000..f589bcf --- /dev/null +++ b/test/src/app.module.ts @@ -0,0 +1,15 @@ +import { Module } from "@nestjs/common"; +import { FirebaseModule } from "../../lib"; +import { CatsModule } from "./cats/cats.module"; + +@Module({ + imports: [ + FirebaseModule.forRoot({ + apiKey: "API-KEY-FOR-TEST", + projectId: "demo-nhogs-nestjs-firebase", + emulator: true, + }), + CatsModule, + ], +}) +export class AppModule {} diff --git a/test/src/cats/cats.controller.ts b/test/src/cats/cats.controller.ts new file mode 100644 index 0000000..668002e --- /dev/null +++ b/test/src/cats/cats.controller.ts @@ -0,0 +1,19 @@ +import { Body, Controller, Get, Post } from '@nestjs/common'; +import { CatsService } from './cats.service'; +import { CreateCatDto } from './dto/create-cat.dto'; +import { Cat } from './models/cat.model'; + +@Controller('cats') +export class CatsController { + constructor(private readonly catsService: CatsService) {} + + @Post() + async create(@Body() createCatDto: CreateCatDto) { + return this.catsService.create(createCatDto); + } + + @Get() + async findAll(): Promise { + return this.catsService.findAll(); + } +} diff --git a/test/src/cats/cats.module.ts b/test/src/cats/cats.module.ts new file mode 100644 index 0000000..5ecc22d --- /dev/null +++ b/test/src/cats/cats.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { CatsController } from "./cats.controller"; +import { CatsService } from "./cats.service"; +import { FirebaseModule } from "../../../lib"; + +@Module({ + imports: [FirebaseModule], + controllers: [CatsController], + providers: [CatsService], +}) +export class CatsModule {} diff --git a/test/src/cats/cats.service.ts b/test/src/cats/cats.service.ts new file mode 100644 index 0000000..845035c --- /dev/null +++ b/test/src/cats/cats.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from "@nestjs/common"; +import { CreateCatDto } from "./dto/create-cat.dto"; +import { Cat } from "./models/cat.model"; +import { FirestoreService } from "../../../lib"; +import CatConverter from "./models/cat.converter"; +import { UpdateCatDto } from "./dto/update-cat.dto"; + +@Injectable() +export class CatsService { + constructor(private readonly firestoreService: FirestoreService) {} + + async create(createCatDto: CreateCatDto): Promise { + return await this.firestoreService.addDoc( + "cats", + createCatDto, + CatConverter + ); + } + + async update(updateCatDto: UpdateCatDto): Promise { + return await this.firestoreService.setDoc( + "cats", + updateCatDto.id, + updateCatDto, + CatConverter + ); + } + + async findAll(): Promise { + return await this.firestoreService.getDocs("cats", CatConverter); + } + + async findOneById(id: string): Promise { + return await this.firestoreService.getDoc("cats", id, CatConverter); + } +} diff --git a/test/src/cats/dto/create-cat.dto.ts b/test/src/cats/dto/create-cat.dto.ts new file mode 100644 index 0000000..28ec646 --- /dev/null +++ b/test/src/cats/dto/create-cat.dto.ts @@ -0,0 +1,5 @@ +export class CreateCatDto { + readonly name: string; + readonly age: number; + readonly breed: string; +} diff --git a/test/src/cats/dto/update-cat.dto.ts b/test/src/cats/dto/update-cat.dto.ts new file mode 100644 index 0000000..a2f5f07 --- /dev/null +++ b/test/src/cats/dto/update-cat.dto.ts @@ -0,0 +1,6 @@ +export class UpdateCatDto { + readonly id: string; + readonly name: string; + readonly age: number; + readonly breed: string; +} diff --git a/test/src/cats/models/cat.converter.ts b/test/src/cats/models/cat.converter.ts new file mode 100644 index 0000000..6fc1842 --- /dev/null +++ b/test/src/cats/models/cat.converter.ts @@ -0,0 +1,39 @@ +import { Cat } from "./cat.model"; +import { + FirestoreDataConverter, + QueryDocumentSnapshot, + DocumentData, + SnapshotOptions, + WithFieldValue, + PartialWithFieldValue, + SetOptions, +} from "@firebase/firestore"; + +class CatConverter implements FirestoreDataConverter { + fromFirestore( + snapshot: QueryDocumentSnapshot, + options?: SnapshotOptions + ): Cat { + const data = snapshot.data(options); + + return { + id: snapshot.id, + name: data.name, + age: data.age, + breed: data.breed, + }; + } + + toFirestore( + modelObject: WithFieldValue | PartialWithFieldValue, + options?: SetOptions + ): DocumentData { + return { + name: modelObject.name, + age: modelObject.age, + breed: modelObject.breed, + }; + } +} + +export default new CatConverter(); \ No newline at end of file diff --git a/test/src/cats/models/cat.model.ts b/test/src/cats/models/cat.model.ts new file mode 100644 index 0000000..c4c1e6c --- /dev/null +++ b/test/src/cats/models/cat.model.ts @@ -0,0 +1,6 @@ +export class Cat { + id?: string; + name: string; + age: number; + breed: string; +}