@starci/hfs 4.7.17 → 4.7.19

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.
package/sync/index.mjs CHANGED
@@ -19,6 +19,7 @@ import { createHash } from 'node:crypto';
19
19
  import { createRequire } from 'node:module';
20
20
  import fs from 'node:fs';
21
21
  import path from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
22
23
  import { braceVariants } from '../runtime/scripts/lib/glob.mjs';
23
24
  import { codecovPaths, coverageComponents, jestCoverage, sonarCoverageExclusions } from '../runtime/scripts/hfs/coverage-scope.mjs';
24
25
  import { APP_SCOPE, SIDES, litePresenceOf, loadSlotManifest, resolveRepoDeclaration, slotInEdition } from '../runtime/scripts/hfs/slots.mjs';
@@ -159,19 +160,19 @@ export async function loadPresets(root) {
159
160
  }
160
161
 
161
162
  /**
162
- * The jest preset's Sonar exclusions as @starci/hfs ships them: `@starci/jest-preset/sonar-exclusions.json`, copied byte for byte into
163
- * packages/hfs/runtime by scripts/hfs/sync-runtime.mjs, so a fresh `starci app scaffold` (nothing installed yet) and an upgrade render the
164
- * same list the app's installed preset answers. A missing copy is a refusal, never a render without it.
163
+ * The jest preset's Sonar exclusions as @starci/hfs ships them: `@starci/jest-preset/sonar-exclusions.cjs` (a require-free module),
164
+ * copied byte for byte into packages/hfs/runtime by scripts/hfs/sync-runtime.mjs, so a fresh `starci app scaffold` (nothing installed
165
+ * yet) and an upgrade render the same list the app's installed preset answers. A missing copy is a refusal, never a render without it.
165
166
  */
166
- const BUNDLED_SONAR_EXCLUSIONS = new URL('../runtime/packages/jest-preset/sonar-exclusions.json', import.meta.url);
167
+ const BUNDLED_SONAR_EXCLUSIONS = new URL('../runtime/packages/jest-preset/sonar-exclusions.cjs', import.meta.url);
167
168
 
168
169
  /** The presets ({ sonarExclusions }) of the jest preset version this @starci/hfs pins, read from its bundled copy. */
169
170
  export function bundledPresets() {
170
171
  let list;
171
172
  try {
172
- list = JSON.parse(fs.readFileSync(BUNDLED_SONAR_EXCLUSIONS, 'utf8'));
173
+ list = createRequire(import.meta.url)(fileURLToPath(BUNDLED_SONAR_EXCLUSIONS));
173
174
  } catch {
174
- throw new SyncError('HFS_SYNC_PRESET_MISSING', '@starci/hfs ships no runtime/packages/jest-preset/sonar-exclusions.json; reinstall @starci/hfs at its published version');
175
+ throw new SyncError('HFS_SYNC_PRESET_MISSING', '@starci/hfs ships no runtime/packages/jest-preset/sonar-exclusions.cjs; reinstall @starci/hfs at its published version');
175
176
  }
176
177
  return { sonarExclusions: list.join(',') };
177
178
  }
@@ -0,0 +1,60 @@
1
+ import { Test } from "@nestjs/testing"
2
+ import { mock } from "@starci/jest-preset"
3
+ import { Secret } from "@modules/platform/config"
4
+ import type { DatabaseConnectionOptions } from "./database.options"
5
+ import type { ConnectionOpener, ConnectionSource } from "./database.port"
6
+ import { migrateConnections } from "./migrate-connections.client"
7
+
8
+ const MIGRATE_CLIENT = Symbol("migrate-connections")
9
+
10
+ const setup = async () => {
11
+ const moduleRef = await Test.createTestingModule({
12
+ providers: [{ provide: MIGRATE_CLIENT, useFactory: () => ({ migrateConnections }) }],
13
+ }).compile()
14
+ return moduleRef.get<{ readonly migrateConnections: typeof migrateConnections }>(MIGRATE_CLIENT)
15
+ }
16
+
17
+ const connection = (name: string): DatabaseConnectionOptions => ({
18
+ name,
19
+ url: new Secret(`postgres://localhost:5432/${name}`),
20
+ entities: [],
21
+ migrations: [],
22
+ })
23
+
24
+ describe("migrateConnections", () => {
25
+ it("runs the pending migrations of each connection in order and answers their names by connection", async () => {
26
+ const client = await setup()
27
+ const primary = mock<ConnectionSource>()
28
+ primary.runMigrations.mockResolvedValue([{ name: "Init1" }])
29
+ const archive = mock<ConnectionSource>()
30
+ archive.runMigrations.mockResolvedValue([])
31
+ const sources = new Map([
32
+ ["primary", primary],
33
+ ["archive", archive],
34
+ ])
35
+ const opener = mock<ConnectionOpener>()
36
+ opener.open.mockImplementation(
37
+ (target: DatabaseConnectionOptions) => sources.get(target.name) ?? mock<ConnectionSource>(),
38
+ )
39
+
40
+ const applied = await client.migrateConnections([connection("primary"), connection("archive")], opener)
41
+
42
+ expect(applied).toEqual({ primary: ["Init1"], archive: [] })
43
+ expect(primary.initialize).toHaveBeenCalledTimes(1)
44
+ expect(primary.destroy).toHaveBeenCalledTimes(1)
45
+ expect(archive.destroy).toHaveBeenCalledTimes(1)
46
+ })
47
+
48
+ it("destroys the data source of a connection whose migration fails, and fails", async () => {
49
+ const client = await setup()
50
+ const failing = mock<ConnectionSource>()
51
+ failing.runMigrations.mockRejectedValue(new TypeError("relation already exists"))
52
+ const opener = mock<ConnectionOpener>()
53
+ opener.open.mockReturnValue(failing)
54
+
55
+ await expect(client.migrateConnections([connection("primary")], opener)).rejects.toThrow(
56
+ "relation already exists",
57
+ )
58
+ expect(failing.destroy).toHaveBeenCalledTimes(1)
59
+ })
60
+ })
@@ -1 +0,0 @@
1
- ["**/*.spec.ts", "**/*.e2e-spec.ts", "**/dist/**", "**/coverage/**"]