@causa/runtime-google 0.39.1 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +7 -14
  2. package/dist/app-check/testing.d.ts +7 -3
  3. package/dist/app-check/testing.js +9 -6
  4. package/dist/firebase/module.d.ts +14 -2
  5. package/dist/firebase/module.js +45 -16
  6. package/dist/firebase/testing.d.ts +7 -4
  7. package/dist/firebase/testing.js +12 -7
  8. package/dist/firestore/testing.d.ts +32 -17
  9. package/dist/firestore/testing.js +45 -29
  10. package/dist/identity-platform/testing.d.ts +21 -6
  11. package/dist/identity-platform/testing.js +34 -9
  12. package/dist/pubsub/publisher.module.d.ts +1 -1
  13. package/dist/pubsub/{testing/fixture.d.ts → testing.d.ts} +64 -48
  14. package/dist/pubsub/{testing/fixture.js → testing.js} +106 -73
  15. package/dist/spanner/column.decorator.d.ts +1 -11
  16. package/dist/spanner/column.decorator.js +2 -7
  17. package/dist/spanner/conversion.d.ts +5 -18
  18. package/dist/spanner/conversion.js +45 -125
  19. package/dist/spanner/entity-manager.d.ts +19 -23
  20. package/dist/spanner/entity-manager.js +26 -60
  21. package/dist/spanner/table-cache.js +0 -3
  22. package/dist/spanner/testing.d.ts +37 -18
  23. package/dist/spanner/testing.js +75 -10
  24. package/dist/spanner/types.d.ts +0 -6
  25. package/dist/testing.d.ts +36 -2
  26. package/dist/testing.js +33 -2
  27. package/dist/transaction/firestore-pubsub/index.d.ts +3 -2
  28. package/dist/transaction/firestore-pubsub/index.js +2 -1
  29. package/dist/transaction/firestore-pubsub/nestjs-collection-resolver.d.ts +1 -1
  30. package/dist/transaction/firestore-pubsub/nestjs-collection-resolver.js +0 -1
  31. package/dist/transaction/firestore-pubsub/readonly-state-transaction.d.ts +37 -0
  32. package/dist/transaction/firestore-pubsub/readonly-state-transaction.js +46 -0
  33. package/dist/transaction/firestore-pubsub/runner.d.ts +6 -4
  34. package/dist/transaction/firestore-pubsub/runner.js +16 -7
  35. package/dist/transaction/firestore-pubsub/state-transaction.d.ts +11 -49
  36. package/dist/transaction/firestore-pubsub/state-transaction.js +19 -42
  37. package/dist/transaction/firestore-pubsub/transaction.d.ts +15 -3
  38. package/dist/transaction/firestore-pubsub/transaction.js +21 -3
  39. package/dist/transaction/firestore-pubsub/types.d.ts +36 -0
  40. package/dist/transaction/firestore-pubsub/types.js +1 -0
  41. package/dist/transaction/index.d.ts +0 -3
  42. package/dist/transaction/index.js +0 -3
  43. package/dist/transaction/spanner-outbox/index.d.ts +3 -1
  44. package/dist/transaction/spanner-outbox/index.js +3 -0
  45. package/dist/transaction/spanner-outbox/readonly-transaction.d.ts +22 -0
  46. package/dist/transaction/spanner-outbox/readonly-transaction.js +25 -0
  47. package/dist/transaction/spanner-outbox/runner.d.ts +7 -18
  48. package/dist/transaction/spanner-outbox/runner.js +13 -6
  49. package/dist/transaction/{spanner-utils.js → spanner-outbox/spanner-utils.js} +1 -1
  50. package/dist/transaction/spanner-outbox/state-transaction.d.ts +20 -0
  51. package/dist/transaction/spanner-outbox/state-transaction.js +34 -0
  52. package/dist/transaction/spanner-outbox/transaction.d.ts +28 -0
  53. package/dist/transaction/spanner-outbox/transaction.js +38 -0
  54. package/package.json +37 -34
  55. package/dist/pubsub/testing/index.d.ts +0 -4
  56. package/dist/pubsub/testing/index.js +0 -2
  57. package/dist/pubsub/testing/requester.d.ts +0 -50
  58. package/dist/pubsub/testing/requester.js +0 -53
  59. package/dist/testing/google-app-fixture.d.ts +0 -191
  60. package/dist/testing/google-app-fixture.js +0 -200
  61. package/dist/testing/index.d.ts +0 -1
  62. package/dist/testing/index.js +0 -1
  63. package/dist/transaction/spanner-pubsub/index.d.ts +0 -2
  64. package/dist/transaction/spanner-pubsub/index.js +0 -2
  65. package/dist/transaction/spanner-pubsub/module.d.ts +0 -14
  66. package/dist/transaction/spanner-pubsub/module.js +0 -21
  67. package/dist/transaction/spanner-pubsub/runner.d.ts +0 -23
  68. package/dist/transaction/spanner-pubsub/runner.js +0 -69
  69. package/dist/transaction/spanner-state-transaction.d.ts +0 -20
  70. package/dist/transaction/spanner-state-transaction.js +0 -35
  71. package/dist/transaction/spanner-transaction.d.ts +0 -16
  72. package/dist/transaction/spanner-transaction.js +0 -20
  73. /package/dist/transaction/{spanner-utils.d.ts → spanner-outbox/spanner-utils.d.ts} +0 -0
package/README.md CHANGED
@@ -52,7 +52,7 @@ The `FirebaseModule` is a NestJS module which exports various Firebase services
52
52
 
53
53
  Outside of a NestJS context, `getDefaultFirebaseApp()` can be used to retrieve a consistently initialized singleton app.
54
54
 
55
- For testing, `overrideFirebaseApp` can be set as an `overrides` option for `makeTestAppFactory()`, which ensures the default Firebase application is used across tests and avoids lifecycle issues (repeatedly creating and tearing down new applications).
55
+ For testing, the `FirebaseFixture` can be used, which ensures the default Firebase application is used across tests and avoids lifecycle issues (repeatedly creating and tearing down new applications).
56
56
 
57
57
  ### Firestore
58
58
 
@@ -60,7 +60,7 @@ The `makeFirestoreDataConverter` is a utility that returns a `FirestoreDataConve
60
60
 
61
61
  The `FirestoreCollectionsModule` builds upon the `FirebaseModule` and provides Firestore collections for the listed document types. In services, the `@InjectFirestoreCollection` decorator can be used to retrieve a Firestore collection, prepared with the aforementioned converter.
62
62
 
63
- Testing utilities are also provided. `clearFirestoreCollection()` can be used in between tests to reinitialize the collection. `overrideFirestoreCollections` can be used as an `overrides` option for `makeTestAppFactory()` to replace collections with temporary ones. This ensures separate collections are used for each test and avoids conflicts.
63
+ Testing utilities are also provided with `FirestoreFixture`. It replaces injected collections with temporary ones, to ensure separate collections are used for each test suite and avoid conflicts. Also, collections are cleared between tests.
64
64
 
65
65
  ### NestJS health checks
66
66
 
@@ -86,9 +86,7 @@ For services being triggered by Pub/Sub messages, the `PubSubEventHandlerModule`
86
86
 
87
87
  The `PubSubHealthIndicator` is a `HealthIndicator` which can be used in a health check controller, such as the `GoogleHealthcheckModule`. It attempts to list topics using the Pub/Sub client to check connectivity to the Pub/Sub API.
88
88
 
89
- To test publishers, the `PubSubFixture` handles the creation and deletion of temporary topics. The `PubSubFixture.createWithOverrider()` method is especially useful when used in combination with the `makeTestAppFactory()` utility.
90
-
91
- To test event handlers, the `makePubSubRequester()` utility returns a function which can be used to make HTTP requests in the same way a Pub/Sub push subscription would.
89
+ To test publishers, the `PubSubFixture` handles the creation and deletion of temporary topics, and provides `expect*` utilities to check for published messages. To test event handlers, it also provides the `makeRequester()` utility, which returns a function that can be used to make HTTP requests in the same way a Pub/Sub push subscription would.
92
90
 
93
91
  ### Spanner
94
92
 
@@ -98,25 +96,20 @@ The `SpannerModule` provides a `Database` instance configured using the `SPANNER
98
96
 
99
97
  The `SpannerHealthIndicator` is a `HealthIndicator` which can be used in a health check controller, such as the `GoogleHealthcheckModule`. It runs a dummy `SELECT 1` query against the database to check connectivity.
100
98
 
101
- For testing, the `createDatabase` utility creates a temporary database, copying the DDL from the configured database (set with the `SPANNER_DATABASE` environment variable). `overrideDatabase` can be used as an `overrides` option for `makeTestAppFactory` to substitute the database with a temporary one.
99
+ For testing, the `createDatabase` utility creates a temporary database, copying the DDL from the configured database (set with the `SPANNER_DATABASE` environment variable). The `SpannerFixture` uses this mechanism, and also clears the specified tables between tests.
102
100
 
103
101
  ### GCP-based Causa transaction runners
104
102
 
105
103
  This package provides the following `TransactionRunner`s:
106
104
 
107
- - `SpannerPubSubTransactionRunner`
108
105
  - `FirestorePubSubTransactionRunner`
109
106
  - `SpannerOutboxTransactionRunner`
110
107
 
111
- The first two use Pub/Sub and a `BufferEventTransaction` to publish events. They only differ by the service used to store the state.
112
-
113
- The `SpannerPubSubTransactionRunner` uses a Spanner transaction as the underlying transaction for the `SpannerTransaction`, while the `FirestorePubSubTransactionRunner` uses a Firestore transaction for the `FirestorePubSubTransaction`. Both state transactions implement the `FindReplaceStateTransaction` interface, and therefore the runners can be used with the `VersionedEntityManager`.
114
-
115
- One feature sets the `FirestorePubSubTransactionRunner` and its `FirestoreStateTransaction` apart: the handling of deleted entities using a separate, "soft-deleted document collection". Entities with a non-null `deletedAt` property are moved to a collection suffixed with `$deleted`, and an `_expirationDate` field is added to them. A TTL is expected to be set on this field. The `@SoftDeletedFirestoreCollection` decorator must be added to document classes that are meant to be handled using the `FirestorePubSubTransactionRunner`.
108
+ The `FirestorePubSubTransactionRunner` uses a Firestore transaction as the underlying state transaction for the `FirestorePubSubTransaction`. One feature sets the `FirestorePubSubTransactionRunner` and its `FirestoreStateTransaction` apart: the handling of deleted entities using a separate, "soft-deleted document collection". Entities with a non-null `deletedAt` property are moved to a collection suffixed with `$deleted`, and an `_expirationDate` field is added to them. A TTL is expected to be set on this field. The `@SoftDeletedFirestoreCollection` decorator must be added to document classes that are meant to be handled using the `FirestorePubSubTransactionRunner`.
116
109
 
117
110
  > [!CAUTION]
118
111
  >
119
- > `SpannerPubSubTransactionRunner` and `FirestorePubSubTransactionRunner` do not provide atomic guarantees between the state and the events being committed. This could result in events being lost, as they are published once the state transaction successfully committed. Prefer the `SpannerOutboxTransactionRunner` when applicable.
112
+ > `FirestorePubSubTransactionRunner` does not provide atomic guarantees between the state and the events being committed. This could result in events being lost, as they are published once the state transaction successfully committed. Prefer the `SpannerOutboxTransactionRunner` when applicable.
120
113
 
121
114
  `SpannerOutboxTransactionRunner` implements the outbox pattern (from the base runtime's `OutboxTransactionRunner`), and uses the default injected `EventPublisher` (which can be the `PubSubPublisher`, if the corresponding module is imported). It requires an outbox table to be created in each database using the runner. See the documentation of the `SpannerOutboxEvent` for more information.
122
115
 
@@ -126,4 +119,4 @@ The `@IsValidFirestoreId` validation decorator checks that a property is a strin
126
119
 
127
120
  ### More testing utilities
128
121
 
129
- Additionally to the testing utilities provided alongside many features in this package, the `GoogleAppFixture` ties many of those together to provide a smooth experience when testing a full NestJS application using Google services (Spanner, Pub/Sub, Firestore, Identity Platform) together. It manages the setup and tear down of all these services locally and provides a single entrypoint to interact with GCP-related fixtures in tests.
122
+ To include all fixtures provided in this package as part of an `AppFixture`, use the `createGoogleFixtures` function, which is a convenience method to create the fixtures with sensible defaults. This will also automatically provide a `VersionedEntityFixture` configured with the `SpannerOutboxTransactionRunner`.
@@ -1,6 +1,6 @@
1
- import type { NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
1
+ import type { Fixture, NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
2
2
  /**
3
- * A {@link NestJsModuleOverrider} that disables the {@link AppCheckGuard}.
3
+ * A {@link Fixture} that disables the {@link AppCheckGuard}.
4
4
  *
5
5
  * If used as an app-level guard, the {@link AppCheckGuard} should be defined as a provider first, and the app guard
6
6
  * should reuse the existing instance:
@@ -9,4 +9,8 @@ import type { NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
9
9
  * { provide: APP_GUARD, useExisting: AppCheckGuard }
10
10
  * ```
11
11
  */
12
- export declare const overrideAppCheck: NestJsModuleOverrider;
12
+ export declare class AppCheckFixture implements Fixture {
13
+ init(): Promise<NestJsModuleOverrider>;
14
+ clear(): Promise<void>;
15
+ delete(): Promise<void>;
16
+ }
@@ -1,6 +1,6 @@
1
1
  import { AppCheckGuard } from './guard.js';
2
2
  /**
3
- * A {@link NestJsModuleOverrider} that disables the {@link AppCheckGuard}.
3
+ * A {@link Fixture} that disables the {@link AppCheckGuard}.
4
4
  *
5
5
  * If used as an app-level guard, the {@link AppCheckGuard} should be defined as a provider first, and the app guard
6
6
  * should reuse the existing instance:
@@ -9,8 +9,11 @@ import { AppCheckGuard } from './guard.js';
9
9
  * { provide: APP_GUARD, useExisting: AppCheckGuard }
10
10
  * ```
11
11
  */
12
- export const overrideAppCheck = (builder) => builder.overrideProvider(AppCheckGuard).useValue({
13
- canActivate() {
14
- return true;
15
- },
16
- });
12
+ export class AppCheckFixture {
13
+ async init() {
14
+ const mockGuard = { canActivate: () => true };
15
+ return (builder) => builder.overrideProvider(AppCheckGuard).useValue(mockGuard);
16
+ }
17
+ async clear() { }
18
+ async delete() { }
19
+ }
@@ -1,9 +1,20 @@
1
1
  import { type DynamicModule } from '@nestjs/common';
2
2
  import { type AppOptions } from 'firebase-admin/app';
3
+ import { type Settings } from 'firebase-admin/firestore';
4
+ /**
5
+ * Options for the various Firebase services (other than the base Firebase app).
6
+ */
7
+ export type FirebaseModuleServiceOptions = {
8
+ /**
9
+ * Options for the Firestore client.
10
+ * The default configuration sets {@link Settings.ignoreUndefinedProperties} to `true`.
11
+ */
12
+ firestore?: Settings;
13
+ };
3
14
  /**
4
15
  * Options when configuring the {@link FirebaseModule}.
5
16
  */
6
- export type FirebaseModuleOptions = AppOptions & {
17
+ export type FirebaseModuleOptions = AppOptions & FirebaseModuleServiceOptions & {
7
18
  /**
8
19
  * The name of the Firebase app to initialize.
9
20
  */
@@ -36,7 +47,8 @@ export declare class FirebaseModule {
36
47
  * {@link initializeApp}.
37
48
  * When testing, this avoids repeatedly initializing the same app, which would result in an error.
38
49
  *
50
+ * @param options Options for the Firebase services (other than the base Firebase app).
39
51
  * @returns The module.
40
52
  */
41
- static forTesting(): DynamicModule;
53
+ static forTesting(options?: FirebaseModuleServiceOptions): DynamicModule;
42
54
  }
@@ -9,22 +9,48 @@ import { Module, } from '@nestjs/common';
9
9
  import { initializeApp } from 'firebase-admin/app';
10
10
  import { AppCheck, getAppCheck } from 'firebase-admin/app-check';
11
11
  import { Auth, getAuth } from 'firebase-admin/auth';
12
- import { Firestore, getFirestore } from 'firebase-admin/firestore';
13
- import { Messaging, getMessaging } from 'firebase-admin/messaging';
12
+ import { Firestore, getFirestore, } from 'firebase-admin/firestore';
13
+ import { getMessaging, Messaging } from 'firebase-admin/messaging';
14
14
  import { getDefaultFirebaseApp } from './app.js';
15
15
  import { FirestoreAdminClient } from './firestore-admin-client.type.js';
16
16
  import { FIREBASE_APP_TOKEN } from './inject-firebase-app.decorator.js';
17
17
  import { FirebaseLifecycleService } from './lifecycle.service.js';
18
+ /**
19
+ * The NestJS injection token for Firestore settings.
20
+ */
21
+ const FIRESTORE_SETTINGS_TOKEN = 'CAUSA_FIRESTORE_SETTINGS';
22
+ /**
23
+ * The default Firestore settings to use when initializing the Firestore client.
24
+ */
25
+ const DEFAULT_FIRESTORE_SETTINGS = {
26
+ ignoreUndefinedProperties: true,
27
+ };
18
28
  /**
19
29
  * The providers for service-specific Firebase clients.
20
- * Those do not have any options as they inherit them from the `App`.
30
+ * Options for the services can be passed using injection tokens.
21
31
  */
22
32
  const childProviders = [
23
33
  { provide: Auth, useFactory: getAuth, inject: [FIREBASE_APP_TOKEN] },
24
34
  {
25
35
  provide: Firestore,
26
- useFactory: getFirestore,
27
- inject: [FIREBASE_APP_TOKEN],
36
+ useFactory: (app, settings) => {
37
+ const firestore = getFirestore(app);
38
+ try {
39
+ // Firestore settings can only be set once, but we cannot know if they've already been set without using private
40
+ // APIs. Calling this several times could occur in testing, when the default app is reused.
41
+ firestore.settings(settings);
42
+ return firestore;
43
+ }
44
+ catch (error) {
45
+ // The Firestore SDK does not type the error more precisely.
46
+ if (error instanceof Error &&
47
+ error.message.includes('Firestore has already been initialized.')) {
48
+ return firestore;
49
+ }
50
+ throw error;
51
+ }
52
+ },
53
+ inject: [FIREBASE_APP_TOKEN, FIRESTORE_SETTINGS_TOKEN],
28
54
  },
29
55
  { provide: AppCheck, useFactory: getAppCheck, inject: [FIREBASE_APP_TOKEN] },
30
56
  {
@@ -40,20 +66,22 @@ const childProviders = [
40
66
  /**
41
67
  * Creates the module metadata for the {@link FirebaseModule}.
42
68
  *
69
+ * @param useDefaultFactory Whether to use {@link getDefaultFirebaseApp} to (re)use the default Firebase app.
43
70
  * @param options Options when configuring the {@link FirebaseModule}.
44
- * If set to `default`, the default Firebase app will be (re)used using {@link getDefaultFirebaseApp}.
71
+ * If the default Firebase app is used, app options are ignored.
45
72
  * @returns The module metadata.
46
73
  */
47
- function createModuleMetadata(options = {}) {
48
- const useDefaultFactory = options === 'default';
49
- const { appName, ...appOptions } = useDefaultFactory
50
- ? {}
51
- : options;
74
+ function createModuleMetadata(useDefaultFactory, options) {
75
+ const { appName, firestore, ...appOptions } = options;
52
76
  const appFactory = useDefaultFactory
53
77
  ? getDefaultFirebaseApp
54
78
  : () => initializeApp(appOptions, appName);
55
79
  const providers = [
56
80
  { provide: FIREBASE_APP_TOKEN, useFactory: appFactory },
81
+ {
82
+ provide: FIRESTORE_SETTINGS_TOKEN,
83
+ useValue: { ...DEFAULT_FIRESTORE_SETTINGS, ...firestore },
84
+ },
57
85
  ...childProviders,
58
86
  ];
59
87
  if (!useDefaultFactory) {
@@ -79,7 +107,7 @@ let FirebaseModule = FirebaseModule_1 = class FirebaseModule {
79
107
  */
80
108
  static forRoot(options = {}) {
81
109
  return {
82
- ...createModuleMetadata(options),
110
+ ...createModuleMetadata(false, options),
83
111
  module: FirebaseModule_1,
84
112
  global: true,
85
113
  };
@@ -92,7 +120,7 @@ let FirebaseModule = FirebaseModule_1 = class FirebaseModule {
92
120
  */
93
121
  static register(options = {}) {
94
122
  return {
95
- ...createModuleMetadata(options),
123
+ ...createModuleMetadata(false, options),
96
124
  module: FirebaseModule_1,
97
125
  };
98
126
  }
@@ -102,17 +130,18 @@ let FirebaseModule = FirebaseModule_1 = class FirebaseModule {
102
130
  * {@link initializeApp}.
103
131
  * When testing, this avoids repeatedly initializing the same app, which would result in an error.
104
132
  *
133
+ * @param options Options for the Firebase services (other than the base Firebase app).
105
134
  * @returns The module.
106
135
  */
107
- static forTesting() {
136
+ static forTesting(options = {}) {
108
137
  return {
109
- ...createModuleMetadata('default'),
138
+ ...createModuleMetadata(true, options),
110
139
  module: FirebaseModule_1,
111
140
  global: true,
112
141
  };
113
142
  }
114
143
  };
115
144
  FirebaseModule = FirebaseModule_1 = __decorate([
116
- Module(createModuleMetadata())
145
+ Module(createModuleMetadata(false, {}))
117
146
  ], FirebaseModule);
118
147
  export { FirebaseModule };
@@ -1,6 +1,9 @@
1
- import type { NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
1
+ import type { Fixture, NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
2
2
  /**
3
- * A {@link NestJsModuleOverrider} that reuses {@link getDefaultFirebaseApp} and prevents the deletion of the Firebase
4
- * application upon shutdown.
3
+ * A {@link Fixture} that reuses the default Firebase application and prevents its deletion upon shutdown.
5
4
  */
6
- export declare const overrideFirebaseApp: NestJsModuleOverrider;
5
+ export declare class FirebaseFixture implements Fixture {
6
+ init(): Promise<NestJsModuleOverrider>;
7
+ clear(): Promise<void>;
8
+ delete(): Promise<void>;
9
+ }
@@ -2,11 +2,16 @@ import { getDefaultFirebaseApp } from './app.js';
2
2
  import { FIREBASE_APP_TOKEN } from './inject-firebase-app.decorator.js';
3
3
  import { FirebaseLifecycleService } from './lifecycle.service.js';
4
4
  /**
5
- * A {@link NestJsModuleOverrider} that reuses {@link getDefaultFirebaseApp} and prevents the deletion of the Firebase
6
- * application upon shutdown.
5
+ * A {@link Fixture} that reuses the default Firebase application and prevents its deletion upon shutdown.
7
6
  */
8
- export const overrideFirebaseApp = (builder) => builder
9
- .overrideProvider(FIREBASE_APP_TOKEN)
10
- .useValue(getDefaultFirebaseApp())
11
- .overrideProvider(FirebaseLifecycleService)
12
- .useValue({});
7
+ export class FirebaseFixture {
8
+ async init() {
9
+ return (builder) => builder
10
+ .overrideProvider(FIREBASE_APP_TOKEN)
11
+ .useValue(getDefaultFirebaseApp())
12
+ .overrideProvider(FirebaseLifecycleService)
13
+ .useValue({});
14
+ }
15
+ async clear() { }
16
+ async delete() { }
17
+ }
@@ -1,5 +1,5 @@
1
- import type { NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
2
- import type { INestApplicationContext, Type } from '@nestjs/common';
1
+ import type { AppFixture, Fixture, NestJsModuleOverrider } from '@causa/runtime/nestjs/testing';
2
+ import type { Type } from '@nestjs/common';
3
3
  import { CollectionReference, Firestore } from 'firebase-admin/firestore';
4
4
  /**
5
5
  * Creates a new collection prefixed with a random ID.
@@ -17,19 +17,34 @@ export declare function createFirestoreTemporaryCollection<T>(firestore: Firesto
17
17
  */
18
18
  export declare function clearFirestoreCollection(collectionRef: CollectionReference): Promise<void>;
19
19
  /**
20
- * Overrides the providers for Firestore collections with temporary collections.
21
- *
22
- * @param documentTypes The types of documents corresponding to Firestore collections, for which collections should be
23
- * overridden.
24
- * @returns The {@link NestJsModuleOverrider} that can be used to override the Firestore collections.
25
- */
26
- export declare function overrideFirestoreCollections(...documentTypes: Type[]): NestJsModuleOverrider;
27
- /**
28
- * Returns the {@link CollectionReference} of the Firestore collection corresponding to the given class.
29
- * Retrieving it from the module or application ensures the "mocked" collection is used.
30
- *
31
- * @param context The NestJS module or application from which the Firestore collection should be retrieved.
32
- * @param documentType The type of document stored in the collection.
33
- * @returns The {@link CollectionReference} of the Firestore collection.
20
+ * A {@link Fixture} that replaces Firestore collections with temporary collections, and clears them when requested.
34
21
  */
35
- export declare function getFirestoreCollectionFromModule<T>(context: INestApplicationContext, documentType: Type<T>): CollectionReference<T>;
22
+ export declare class FirestoreFixture implements Fixture {
23
+ /**
24
+ * The types of documents that should be stored in temporary collections and cleared.
25
+ */
26
+ readonly types: Type[];
27
+ /**
28
+ * The parent {@link AppFixture}.
29
+ */
30
+ private appFixture;
31
+ constructor(
32
+ /**
33
+ * The types of documents that should be stored in temporary collections and cleared.
34
+ */
35
+ types: Type[]);
36
+ init(appFixture: AppFixture): Promise<NestJsModuleOverrider>;
37
+ clear(): Promise<void>;
38
+ delete(): Promise<void>;
39
+ /**
40
+ * The underlying {@link Firestore} instance used by this fixture.
41
+ */
42
+ get firestore(): Firestore;
43
+ /**
44
+ * Returns the (temporary) collection for the given document type.
45
+ *
46
+ * @param documentType The type of the document.
47
+ * @returns The {@link CollectionReference} for the given document type.
48
+ */
49
+ collection<T>(documentType: Type<T>): CollectionReference<T>;
50
+ }
@@ -26,37 +26,53 @@ export function createFirestoreTemporaryCollection(firestore, documentType) {
26
26
  export async function clearFirestoreCollection(collectionRef) {
27
27
  const batch = collectionRef.firestore.batch();
28
28
  const documents = await collectionRef.listDocuments();
29
- documents.forEach((document) => batch.delete(document));
29
+ documents.forEach((d) => batch.delete(d));
30
30
  await batch.commit();
31
31
  }
32
32
  /**
33
- * Overrides the providers for Firestore collections with temporary collections.
34
- *
35
- * @param documentTypes The types of documents corresponding to Firestore collections, for which collections should be
36
- * overridden.
37
- * @returns The {@link NestJsModuleOverrider} that can be used to override the Firestore collections.
38
- */
39
- export function overrideFirestoreCollections(...documentTypes) {
40
- return (builder) => {
41
- documentTypes.forEach((documentType) => {
42
- builder = builder
43
- .overrideProvider(getFirestoreCollectionInjectionName(documentType))
44
- .useFactory({
45
- factory: (firestore) => createFirestoreTemporaryCollection(firestore, documentType),
46
- inject: [Firestore],
47
- });
48
- });
49
- return builder;
50
- };
51
- }
52
- /**
53
- * Returns the {@link CollectionReference} of the Firestore collection corresponding to the given class.
54
- * Retrieving it from the module or application ensures the "mocked" collection is used.
55
- *
56
- * @param context The NestJS module or application from which the Firestore collection should be retrieved.
57
- * @param documentType The type of document stored in the collection.
58
- * @returns The {@link CollectionReference} of the Firestore collection.
33
+ * A {@link Fixture} that replaces Firestore collections with temporary collections, and clears them when requested.
59
34
  */
60
- export function getFirestoreCollectionFromModule(context, documentType) {
61
- return context.get(getFirestoreCollectionInjectionName(documentType));
35
+ export class FirestoreFixture {
36
+ types;
37
+ /**
38
+ * The parent {@link AppFixture}.
39
+ */
40
+ appFixture;
41
+ constructor(
42
+ /**
43
+ * The types of documents that should be stored in temporary collections and cleared.
44
+ */
45
+ types) {
46
+ this.types = types;
47
+ }
48
+ async init(appFixture) {
49
+ this.appFixture = appFixture;
50
+ return (builder) => this.types.reduce((builder, t) => builder
51
+ .overrideProvider(getFirestoreCollectionInjectionName(t))
52
+ .useFactory({
53
+ factory: (f) => createFirestoreTemporaryCollection(f, t),
54
+ inject: [Firestore],
55
+ }), builder);
56
+ }
57
+ async clear() {
58
+ await Promise.all(this.types.map((t) => clearFirestoreCollection(this.collection(t))));
59
+ }
60
+ async delete() {
61
+ this.appFixture = undefined;
62
+ }
63
+ /**
64
+ * The underlying {@link Firestore} instance used by this fixture.
65
+ */
66
+ get firestore() {
67
+ return this.appFixture.get(Firestore);
68
+ }
69
+ /**
70
+ * Returns the (temporary) collection for the given document type.
71
+ *
72
+ * @param documentType The type of the document.
73
+ * @returns The {@link CollectionReference} for the given document type.
74
+ */
75
+ collection(documentType) {
76
+ return this.appFixture.get(getFirestoreCollectionInjectionName(documentType));
77
+ }
62
78
  }
@@ -1,15 +1,16 @@
1
1
  import type { User } from '@causa/runtime';
2
+ import type { AppFixture, Fixture } from '@causa/runtime/nestjs/testing';
2
3
  import { Auth } from 'firebase-admin/auth';
3
4
  import jwt from 'jsonwebtoken';
4
5
  /**
5
- * A helper to create and delete Identity Platform users in the emulator.
6
+ * A {@link Fixture} to create and delete Identity Platform users in the emulator.
6
7
  * It also creates an ID token for them.
7
8
  */
8
- export declare class AuthUsersFixture {
9
+ export declare class AuthUsersFixture implements Fixture {
9
10
  /**
10
- * The Firebase Auth client to use.
11
+ * The parent {@link AppFixture}.
11
12
  */
12
- readonly auth: Auth;
13
+ private appFixture;
13
14
  /**
14
15
  * The list of created users.
15
16
  */
@@ -18,10 +19,21 @@ export declare class AuthUsersFixture {
18
19
  * The base payload required to form a JWT.
19
20
  */
20
21
  private readonly jwtBasePayload;
22
+ /**
23
+ * The Firebase Auth client to use.
24
+ * This is lazily initialized when the `auth` property is accessed, which avoids trying to fetch it during
25
+ * {@link AuthUsersFixture.init} and / or {@link AuthUsersFixture.delete}.
26
+ */
27
+ private lazyAuth?;
21
28
  /**
22
29
  * Creates a new {@link AuthUsersFixture}.
23
30
  */
24
31
  constructor();
32
+ init(appFixture: AppFixture): Promise<undefined>;
33
+ /**
34
+ * The Firebase Auth client to use.
35
+ */
36
+ get auth(): Auth;
25
37
  /**
26
38
  * Creates a new user if it doesn't already exist, and generates a corresponding JWT.
27
39
  *
@@ -33,8 +45,11 @@ export declare class AuthUsersFixture {
33
45
  user: User;
34
46
  token: string;
35
47
  }>;
48
+ clear(): Promise<void>;
36
49
  /**
37
- * Deletes users created by this fixture from the Identity Platform emulator.
50
+ * Deletes all users created by this fixture.
51
+ * This does not delete the fixture itself.
38
52
  */
39
- deleteAll(): Promise<void>;
53
+ deleteUsers(): Promise<void>;
54
+ delete(): Promise<void>;
40
55
  }
@@ -1,16 +1,15 @@
1
- import { Auth, getAuth } from 'firebase-admin/auth';
1
+ import { Auth } from 'firebase-admin/auth';
2
2
  import jwt from 'jsonwebtoken';
3
3
  import * as uuid from 'uuid';
4
- import { getDefaultFirebaseApp } from '../firebase/index.js';
5
4
  /**
6
- * A helper to create and delete Identity Platform users in the emulator.
5
+ * A {@link Fixture} to create and delete Identity Platform users in the emulator.
7
6
  * It also creates an ID token for them.
8
7
  */
9
8
  export class AuthUsersFixture {
10
9
  /**
11
- * The Firebase Auth client to use.
10
+ * The parent {@link AppFixture}.
12
11
  */
13
- auth;
12
+ appFixture;
14
13
  /**
15
14
  * The list of created users.
16
15
  */
@@ -19,17 +18,34 @@ export class AuthUsersFixture {
19
18
  * The base payload required to form a JWT.
20
19
  */
21
20
  jwtBasePayload;
21
+ /**
22
+ * The Firebase Auth client to use.
23
+ * This is lazily initialized when the `auth` property is accessed, which avoids trying to fetch it during
24
+ * {@link AuthUsersFixture.init} and / or {@link AuthUsersFixture.delete}.
25
+ */
26
+ lazyAuth;
22
27
  /**
23
28
  * Creates a new {@link AuthUsersFixture}.
24
29
  */
25
30
  constructor() {
26
- this.auth = getAuth(getDefaultFirebaseApp());
27
31
  const projectId = process.env.GOOGLE_CLOUD_PROJECT ?? '';
28
32
  this.jwtBasePayload = {
29
33
  aud: projectId,
30
34
  iss: `https://securetoken.google.com/${projectId}`,
31
35
  };
32
36
  }
37
+ async init(appFixture) {
38
+ this.appFixture = appFixture;
39
+ }
40
+ /**
41
+ * The Firebase Auth client to use.
42
+ */
43
+ get auth() {
44
+ if (!this.lazyAuth) {
45
+ this.lazyAuth = this.appFixture.get(Auth);
46
+ }
47
+ return this.lazyAuth;
48
+ }
33
49
  /**
34
50
  * Creates a new user if it doesn't already exist, and generates a corresponding JWT.
35
51
  *
@@ -52,11 +68,20 @@ export class AuthUsersFixture {
52
68
  const token = jwt.sign({ ...this.jwtBasePayload, sub: user.id, ...customClaims }, null, { ...tokenOptions, algorithm: 'none' });
53
69
  return { user, token };
54
70
  }
71
+ async clear() { }
55
72
  /**
56
- * Deletes users created by this fixture from the Identity Platform emulator.
73
+ * Deletes all users created by this fixture.
74
+ * This does not delete the fixture itself.
57
75
  */
58
- async deleteAll() {
59
- await this.auth.deleteUsers(this.users.map((user) => user.id));
76
+ async deleteUsers() {
77
+ if (this.users.length === 0) {
78
+ return;
79
+ }
80
+ await this.auth.deleteUsers(this.users.map(({ id }) => id));
60
81
  this.users.length = 0;
61
82
  }
83
+ async delete() {
84
+ await this.deleteUsers();
85
+ this.appFixture = undefined;
86
+ }
62
87
  }
@@ -14,5 +14,5 @@ export declare class PubSubPublisherModule {
14
14
  * @param options Options for the {@link PubSubPublisher}.
15
15
  * @returns The module.
16
16
  */
17
- static forRoot(options?: Pick<PubSubPublisherOptions, 'publishOptions' | 'serializer'>): DynamicModule;
17
+ static forRoot(options?: Omit<PubSubPublisherOptions, 'pubSub' | 'configurationGetter'>): DynamicModule;
18
18
  }