sonamu 0.9.20 → 0.10.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.
- package/dist/_virtual/_rolldown/runtime.js +36 -0
- package/dist/ai/agents/agent.js +5 -7
- package/dist/ai/agents/index.js +1 -2
- package/dist/ai/agents/types.js +1 -1
- package/dist/ai/index.js +1 -2
- package/dist/ai/providers/rtzr/api.js +2 -3
- package/dist/ai/providers/rtzr/error.js +14 -29
- package/dist/ai/providers/rtzr/index.js +1 -2
- package/dist/ai/providers/rtzr/model.js +13 -20
- package/dist/ai/providers/rtzr/options.js +2 -3
- package/dist/ai/providers/rtzr/provider.js +2 -3
- package/dist/ai/providers/rtzr/utils.js +12 -21
- package/dist/api/base-frame.js +4 -4
- package/dist/api/caster.js +21 -38
- package/dist/api/code-converters.js +41 -98
- package/dist/api/config.d.ts +1 -10
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +9 -8
- package/dist/api/context.js +2 -3
- package/dist/api/decorators.js +80 -116
- package/dist/api/index.js +2 -3
- package/dist/api/secret.js +6 -10
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +210 -406
- package/dist/api/validator.js +5 -8
- package/dist/api/websocket-helpers.js +22 -33
- package/dist/auth/audit-log/builders.d.ts.map +1 -1
- package/dist/auth/audit-log/builders.js +4 -3
- package/dist/auth/audit-log/client-ip.d.ts +3 -0
- package/dist/auth/audit-log/client-ip.d.ts.map +1 -0
- package/dist/auth/audit-log/client-ip.js +27 -0
- package/dist/auth/audit-log/events.d.ts +1 -0
- package/dist/auth/audit-log/events.d.ts.map +1 -1
- package/dist/auth/audit-log/events.js +2 -2
- package/dist/auth/audit-log/plugin.js +30 -61
- package/dist/auth/audit-log-ingestor.d.ts.map +1 -1
- package/dist/auth/audit-log-ingestor.js +26 -43
- package/dist/auth/auth-generator.js +16 -41
- package/dist/auth/better-auth-entities.js +3 -4
- package/dist/auth/better-auth-request.d.ts +3 -0
- package/dist/auth/better-auth-request.d.ts.map +1 -0
- package/dist/auth/better-auth-request.js +23 -0
- package/dist/auth/index.js +2 -3
- package/dist/auth/knex-adapter.js +18 -45
- package/dist/auth/plugins/entity-definitions/admin.js +2 -2
- package/dist/auth/plugins/entity-definitions/anonymous.js +2 -2
- package/dist/auth/plugins/entity-definitions/api-key.js +2 -2
- package/dist/auth/plugins/entity-definitions/audit-log.js +2 -2
- package/dist/auth/plugins/entity-definitions/index.js +2 -3
- package/dist/auth/plugins/entity-definitions/jwt.js +2 -2
- package/dist/auth/plugins/entity-definitions/organization.js +2 -2
- package/dist/auth/plugins/entity-definitions/passkey.js +2 -2
- package/dist/auth/plugins/entity-definitions/phone-number.js +2 -2
- package/dist/auth/plugins/entity-definitions/sso.js +2 -2
- package/dist/auth/plugins/entity-definitions/two-factor.js +2 -2
- package/dist/auth/plugins/entity-definitions/types.js +1 -1
- package/dist/auth/plugins/entity-definitions/username.js +2 -2
- package/dist/auth/plugins/index.js +1 -2
- package/dist/auth/plugins/wrappers/admin.js +2 -3
- package/dist/auth/plugins/wrappers/anonymous.js +2 -3
- package/dist/auth/plugins/wrappers/api-key.js +2 -3
- package/dist/auth/plugins/wrappers/index.js +1 -2
- package/dist/auth/plugins/wrappers/jwt.js +2 -3
- package/dist/auth/plugins/wrappers/organization.js +2 -3
- package/dist/auth/plugins/wrappers/passkey.js +2 -3
- package/dist/auth/plugins/wrappers/phone-number.js +2 -3
- package/dist/auth/plugins/wrappers/sso.js +2 -3
- package/dist/auth/plugins/wrappers/two-factor.js +2 -3
- package/dist/auth/plugins/wrappers/username.js +2 -3
- package/dist/bin/build-config.js +2 -2
- package/dist/bin/cli.js +152 -259
- package/dist/bin/fixture.d.ts.map +1 -1
- package/dist/bin/fixture.js +55 -97
- package/dist/bin/hmr-hook-register.js +3 -3
- package/dist/bin/migrate-targets.d.ts +3 -0
- package/dist/bin/migrate-targets.d.ts.map +1 -0
- package/dist/bin/migrate-targets.js +11 -0
- package/dist/bin/test-command.js +25 -55
- package/dist/bin/ts-loader-register.js +5 -6
- package/dist/bin/ts-loader-registration.js +6 -13
- package/dist/cache/cache-manager.js +3 -4
- package/dist/cache/decorator.js +11 -21
- package/dist/cache/drivers.js +2 -3
- package/dist/cache/index.js +2 -3
- package/dist/cache/types.js +1 -1
- package/dist/cache-control/cache-control.js +21 -34
- package/dist/cache-control/types.js +1 -1
- package/dist/compress/compress.js +10 -10
- package/dist/compress/index.js +1 -2
- package/dist/compress/types.js +1 -1
- package/dist/cone/cone-generator.js +25 -63
- package/dist/database/_batch_update.js +26 -46
- package/dist/database/base-model.js +44 -97
- package/dist/database/base-model.types.js +1 -1
- package/dist/database/db.d.ts +8 -14
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +127 -72
- package/dist/database/knex.js +5 -8
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/database/puri-wrapper.js +11 -15
- package/dist/database/puri.js +117 -234
- package/dist/database/puri.types.js +3 -4
- package/dist/database/transaction-context.js +4 -5
- package/dist/database/upsert-builder.js +109 -176
- package/dist/dict/en.d.ts +1 -0
- package/dist/dict/en.d.ts.map +1 -1
- package/dist/dict/en.js +4 -4
- package/dist/dict/index.js +2 -3
- package/dist/dict/ko.d.ts +1 -0
- package/dist/dict/ko.d.ts.map +1 -1
- package/dist/dict/ko.js +4 -4
- package/dist/dict/rc-keys.js +3 -4
- package/dist/dict/sd.js +8 -19
- package/dist/dict/sonamu-dictionary.js +141 -284
- package/dist/dict/types.js +1 -1
- package/dist/dict/utils.js +4 -5
- package/dist/entity/entity-manager.d.ts +2 -2
- package/dist/entity/entity-manager.js +34 -82
- package/dist/entity/entity-template-cone.js +33 -66
- package/dist/entity/entity.js +157 -311
- package/dist/env.d.ts +14 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +75 -0
- package/dist/exceptions/error-handler.js +2 -3
- package/dist/exceptions/so-exceptions.js +7 -5
- package/dist/filter/index.js +1 -2
- package/dist/filter/types.js +3 -4
- package/dist/filter/utils.js +21 -54
- package/dist/index.js +9 -8
- package/dist/logger/category.js +6 -12
- package/dist/logger/configure.js +23 -34
- package/dist/migration/code-generation.js +147 -315
- package/dist/migration/index-where-predicate.js +52 -144
- package/dist/migration/migration-set.js +19 -33
- package/dist/migration/migrator.d.ts +2 -0
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +69 -53
- package/dist/migration/postgresql-schema-reader.js +126 -225
- package/dist/migration/slack-confirm.d.ts +1 -0
- package/dist/migration/slack-confirm.d.ts.map +1 -1
- package/dist/migration/slack-confirm.js +28 -38
- package/dist/migration/types.js +1 -1
- package/dist/naite/messaging-types.js +1 -1
- package/dist/naite/naite-reporter.js +15 -32
- package/dist/naite/naite.js +43 -76
- package/dist/ssr/index.js +6 -9
- package/dist/ssr/registry.js +10 -18
- package/dist/ssr/renderer.js +10 -21
- package/dist/ssr/types.js +1 -1
- package/dist/storage/base-file.js +5 -10
- package/dist/storage/buffered-file.js +3 -4
- package/dist/storage/drivers.js +2 -3
- package/dist/storage/index.js +2 -3
- package/dist/storage/s3-driver.js +5 -9
- package/dist/storage/storage-manager.js +5 -5
- package/dist/storage/types.js +1 -1
- package/dist/storage/uploaded-file.js +4 -6
- package/dist/stream/index.js +1 -2
- package/dist/stream/sse.js +8 -13
- package/dist/stream/ws-audience-resolver.js +5 -5
- package/dist/stream/ws-audience.js +3 -4
- package/dist/stream/ws-cluster-bus.js +3 -4
- package/dist/stream/ws-core.js +1 -1
- package/dist/stream/ws-delivery.js +11 -25
- package/dist/stream/ws-local-connection-store.js +9 -18
- package/dist/stream/ws-presence-store.js +43 -97
- package/dist/stream/ws-registry.js +17 -22
- package/dist/stream/ws-telemetry-memory.js +38 -45
- package/dist/stream/ws-telemetry-trace.js +4 -6
- package/dist/stream/ws-telemetry.js +82 -135
- package/dist/stream/ws.js +47 -91
- package/dist/syncer/api-parser.js +81 -147
- package/dist/syncer/checksum.js +14 -32
- package/dist/syncer/code-generator.js +29 -47
- package/dist/syncer/entity-operations.js +17 -27
- package/dist/syncer/event-batcher.js +8 -15
- package/dist/syncer/file-patterns.js +3 -4
- package/dist/syncer/file-tracking.js +6 -10
- package/dist/syncer/index.js +1 -2
- package/dist/syncer/module-loader.js +10 -26
- package/dist/syncer/syncer-actions.js +19 -37
- package/dist/syncer/syncer.js +46 -98
- package/dist/syncer/watcher.js +12 -26
- package/dist/tasks/decorator.js +7 -11
- package/dist/tasks/step-wrapper.js +7 -8
- package/dist/tasks/workflow-manager.js +18 -25
- package/dist/template/entity-converter.js +40 -64
- package/dist/template/helpers.js +32 -63
- package/dist/template/implementations/entity.template.js +7 -11
- package/dist/template/implementations/entry-server.template.js +2 -3
- package/dist/template/implementations/generated.template.js +25 -51
- package/dist/template/implementations/generated_http.template.js +31 -58
- package/dist/template/implementations/generated_sso.template.js +45 -85
- package/dist/template/implementations/init_types.template.js +4 -7
- package/dist/template/implementations/model.template.js +5 -10
- package/dist/template/implementations/model_test.template.js +2 -3
- package/dist/template/implementations/queries.template.js +4 -7
- package/dist/template/implementations/sd.template.js +17 -35
- package/dist/template/implementations/services.template.js +18 -30
- package/dist/template/implementations/view_form.template.js +72 -125
- package/dist/template/implementations/view_id_all_select.template.js +2 -3
- package/dist/template/implementations/view_list.template.js +86 -143
- package/dist/template/implementations/view_search_input.template.js +2 -3
- package/dist/template/index.js +5 -8
- package/dist/template/template-manager.js +13 -26
- package/dist/template/template-types.js +2 -3
- package/dist/template/template.js +7 -11
- package/dist/template/zod-converter.js +173 -348
- package/dist/testing/_relation-graph.js +18 -37
- package/dist/testing/bootstrap.js +5 -8
- package/dist/testing/data-explorer.js +34 -78
- package/dist/testing/dev-test-routes.js +54 -60
- package/dist/testing/dev-vitest-manager.js +33 -84
- package/dist/testing/faker-mappings.js +3 -4
- package/dist/testing/fixture-generator.d.ts +2 -1
- package/dist/testing/fixture-generator.d.ts.map +1 -1
- package/dist/testing/fixture-generator.js +159 -321
- package/dist/testing/fixture-loader.js +2 -2
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +124 -227
- package/dist/testing/global-setup.d.ts.map +1 -1
- package/dist/testing/global-setup.js +29 -17
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -4
- package/dist/testing/naite-vitest-reporter.js +2 -3
- package/dist/testing/parallel-db-manager.js +5 -3
- package/dist/testing/vitest-helpers.d.ts.map +1 -1
- package/dist/testing/vitest-helpers.js +15 -12
- package/dist/types/types.d.ts +14 -14
- package/dist/types/types.js +27 -50
- package/dist/ui/ai-api.js +6 -11
- package/dist/ui/ai-client.js +86 -134
- package/dist/ui/api.js +100 -196
- package/dist/ui/cdd-service.js +78 -130
- package/dist/ui/cdd-types.js +1 -1
- package/dist/ui-web/assets/{index-Df8q-fhb.js → index-DFStGyd0.js} +49 -49
- package/dist/ui-web/assets/index-Dx4ap5i4.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/dist/utils/async-utils.js +13 -25
- package/dist/utils/class-name.js +3 -4
- package/dist/utils/console-util.js +11 -26
- package/dist/utils/controller.d.ts.map +1 -1
- package/dist/utils/controller.js +14 -12
- package/dist/utils/esm-utils.js +5 -8
- package/dist/utils/formatter.js +10 -22
- package/dist/utils/fs-utils.js +14 -25
- package/dist/utils/lodash-able.js +3 -4
- package/dist/utils/model.js +7 -14
- package/dist/utils/object-utils.js +41 -73
- package/dist/utils/path-utils.js +5 -9
- package/dist/utils/process-utils.js +4 -7
- package/dist/utils/sql-parser.js +6 -13
- package/dist/utils/type-utils.js +16 -26
- package/dist/utils/utils.js +18 -40
- package/dist/utils/zod-error.js +9 -16
- package/dist/vector/chunking.js +24 -37
- package/dist/vector/config.js +2 -2
- package/dist/vector/embedding.js +8 -19
- package/dist/vector/index.js +1 -2
- package/dist/vector/types.js +1 -1
- package/package.json +8 -8
- package/src/__tests__/env.test.ts +127 -0
- package/src/api/__tests__/config.test.ts +10 -1
- package/src/api/config.ts +4 -12
- package/src/api/sonamu.ts +28 -33
- package/src/auth/audit-log/builders.ts +3 -0
- package/src/auth/audit-log/client-ip.ts +38 -0
- package/src/auth/audit-log/events.ts +1 -0
- package/src/auth/audit-log-ingestor.ts +22 -15
- package/src/auth/better-auth-request.ts +20 -0
- package/src/bin/__tests__/migrate-targets.test.ts +28 -0
- package/src/bin/__tests__/test-command.test.ts +82 -1
- package/src/bin/cli.ts +9 -18
- package/src/bin/fixture.ts +5 -4
- package/src/bin/migrate-targets.ts +7 -0
- package/src/bin/test-command.ts +2 -2
- package/src/database/__tests__/db.test.ts +175 -0
- package/src/database/db.ts +193 -71
- package/src/dict/en.ts +2 -0
- package/src/dict/ko.ts +2 -0
- package/src/env.ts +123 -0
- package/src/migration/__tests__/migrator.test.ts +149 -0
- package/src/migration/migrator.ts +74 -17
- package/src/migration/slack-confirm.ts +21 -0
- package/src/skills/sonamu/database.md +1 -1
- package/src/skills/sonamu/puri.md +33 -0
- package/src/skills/sonamu/testing-devrunner.md +1 -1
- package/src/stream/ws-telemetry-memory.ts +2 -2
- package/src/syncer/checksum.ts +7 -12
- package/src/testing/fixture-generator.ts +2 -1
- package/src/testing/fixture-manager.ts +3 -4
- package/src/testing/global-setup.ts +42 -18
- package/src/testing/index.ts +1 -0
- package/src/testing/vitest-helpers.ts +14 -0
- package/src/utils/controller.ts +14 -7
- package/tsdown.api.config.ts +6 -0
- package/dist/_virtual/rolldown_runtime.js +0 -39
- package/dist/ui-web/assets/index-D4rYm-Xz.css +0 -1
package/src/database/db.ts
CHANGED
|
@@ -4,13 +4,10 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
4
4
|
import { type Knex } from "knex";
|
|
5
5
|
|
|
6
6
|
import { type DatabaseConfig, type SonamuConfig } from "../api/config";
|
|
7
|
+
import { getSonamuEnvironment, type EnvironmentSnapshots, type SonamuEnvironment } from "../env";
|
|
7
8
|
import { createKnexInstance } from "./knex";
|
|
8
9
|
import { TransactionContext } from "./transaction-context";
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* 여러 설정 객체를 순차적으로 deep merge합니다.
|
|
12
|
-
* undefined/null인 인자는 무시됩니다.
|
|
13
|
-
*/
|
|
14
11
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
15
12
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
13
|
}
|
|
@@ -44,20 +41,115 @@ function mergeConfigs<T extends object>(...configs: (Partial<T> | undefined | nu
|
|
|
44
41
|
return merged as T;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
export type
|
|
44
|
+
export type SonamuMainDBPreset = "test" | "fixture" | SonamuEnvironment;
|
|
45
|
+
export type SonamuReadonlyDBPreset =
|
|
46
|
+
| "test_readonly"
|
|
47
|
+
| "development_readonly"
|
|
48
|
+
| "staging_readonly"
|
|
49
|
+
| "production_readonly";
|
|
50
|
+
export type SonamuDBPreset = SonamuMainDBPreset | SonamuReadonlyDBPreset;
|
|
51
|
+
export type DBPreset = "w" | "r" | SonamuDBPreset;
|
|
48
52
|
|
|
49
|
-
export type SonamuDBConfig =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
53
|
+
export type SonamuDBConfig = Record<SonamuDBPreset, Knex.Config>;
|
|
54
|
+
|
|
55
|
+
function isConcretePreset(value: DBPreset): value is SonamuDBPreset {
|
|
56
|
+
return value !== "w" && value !== "r";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getReadonlyPreset(environment: SonamuEnvironment): SonamuReadonlyDBPreset {
|
|
60
|
+
return `${environment}_readonly` as SonamuReadonlyDBPreset;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getProjectDatabaseBaseName(projectName?: string): string {
|
|
64
|
+
return (projectName ?? "sonamu")
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
67
|
+
.replace(/^_+|_+$/g, "");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function numberFromEnv(value: string | undefined, fallback: number): number {
|
|
71
|
+
if (value === undefined || value === "") {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const parsed = Number(value);
|
|
76
|
+
if (Number.isNaN(parsed)) {
|
|
77
|
+
throw new Error(`Invalid database port: ${value}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function connectionFromEnv(options: {
|
|
84
|
+
baseName: string;
|
|
85
|
+
suffix: SonamuMainDBPreset;
|
|
86
|
+
baseConnection?: Knex.PgConnectionConfig;
|
|
87
|
+
prefix?: "SONAMU_DB_READONLY" | "SONAMU_DB_FIXTURE";
|
|
88
|
+
env?: NodeJS.ProcessEnv;
|
|
89
|
+
}): Knex.PgConnectionConfig {
|
|
90
|
+
const {
|
|
91
|
+
baseName,
|
|
92
|
+
suffix,
|
|
93
|
+
baseConnection = {},
|
|
94
|
+
prefix = "SONAMU_DB",
|
|
95
|
+
env = process.env,
|
|
96
|
+
} = options;
|
|
97
|
+
const read = (key: "HOST" | "PORT" | "USER" | "PASSWORD" | "NAME") => {
|
|
98
|
+
const prefixedValue = env[`${prefix}_${key}`];
|
|
99
|
+
if (prefixedValue !== undefined) {
|
|
100
|
+
return prefixedValue;
|
|
101
|
+
}
|
|
102
|
+
if (prefix === "SONAMU_DB_FIXTURE" && key === "NAME") {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return env[`SONAMU_DB_${key}`];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...baseConnection,
|
|
111
|
+
host: read("HOST") ?? baseConnection.host ?? "0.0.0.0",
|
|
112
|
+
port: numberFromEnv(read("PORT"), baseConnection.port ?? 5432),
|
|
113
|
+
user: read("USER") ?? baseConnection.user ?? "postgres",
|
|
114
|
+
password: read("PASSWORD") ?? baseConnection.password,
|
|
115
|
+
database: read("NAME") ?? `${baseName}_${suffix}`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function neutralizeEnvironmentConnectionFields(
|
|
120
|
+
connection: Knex.PgConnectionConfig | undefined,
|
|
121
|
+
): Knex.PgConnectionConfig | undefined {
|
|
122
|
+
if (connection === undefined) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const neutralConnection = { ...connection };
|
|
127
|
+
delete neutralConnection.host;
|
|
128
|
+
delete neutralConnection.port;
|
|
129
|
+
delete neutralConnection.user;
|
|
130
|
+
delete neutralConnection.password;
|
|
131
|
+
delete neutralConnection.database;
|
|
132
|
+
|
|
133
|
+
return neutralConnection;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function assertNoLegacyDatabaseConfig(config: SonamuConfig["database"]): void {
|
|
137
|
+
const legacyConfig = config as SonamuConfig["database"] & {
|
|
138
|
+
name?: unknown;
|
|
139
|
+
environments?: unknown;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (legacyConfig.name !== undefined || legacyConfig.environments !== undefined) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"Sonamu database.name and database.environments were removed. Use SONAMU_DB_* dotenv variables instead.",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
57
148
|
|
|
58
149
|
export class DBClass {
|
|
59
150
|
private wdb?: Knex;
|
|
60
151
|
private rdb?: Knex;
|
|
152
|
+
private presetDBs: Map<SonamuDBPreset, Knex> = new Map();
|
|
61
153
|
private workerDBs: Map<number, Knex> = new Map();
|
|
62
154
|
private currentConfig: SonamuDBConfig | null = null;
|
|
63
155
|
|
|
@@ -86,14 +178,21 @@ export class DBClass {
|
|
|
86
178
|
getDB(which: DBPreset): Knex {
|
|
87
179
|
const dbConfig = this.getCurrentConfig();
|
|
88
180
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
181
|
+
if (isConcretePreset(which)) {
|
|
182
|
+
if (!this.presetDBs.has(which)) {
|
|
183
|
+
this.presetDBs.set(which, createKnexInstance(dbConfig[which]));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const db = this.presetDBs.get(which);
|
|
187
|
+
assert(db, `DB preset ${which} not found`);
|
|
188
|
+
return db;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (getSonamuEnvironment() === "test") {
|
|
92
192
|
if (process.env.SONAMU_WORKER_DB === "true") {
|
|
93
193
|
return this.getWorkerDB(dbConfig);
|
|
94
194
|
}
|
|
95
195
|
|
|
96
|
-
// 기존 단일 테스트 로직
|
|
97
196
|
if (this.testTransaction) {
|
|
98
197
|
return this.testTransaction;
|
|
99
198
|
} else if (this.wdb) {
|
|
@@ -101,7 +200,6 @@ export class DBClass {
|
|
|
101
200
|
} else {
|
|
102
201
|
this.wdb = createKnexInstance({
|
|
103
202
|
...dbConfig.test,
|
|
104
|
-
// 단일 풀
|
|
105
203
|
pool: {
|
|
106
204
|
min: 1,
|
|
107
205
|
max: 1,
|
|
@@ -121,19 +219,13 @@ export class DBClass {
|
|
|
121
219
|
return this[instanceName];
|
|
122
220
|
}
|
|
123
221
|
|
|
124
|
-
/**
|
|
125
|
-
* 병렬 테스트에서 worker별 DB 인스턴스를 반환합니다.
|
|
126
|
-
* VITEST_POOL_ID 환경변수로 worker를 식별하여 해당 DB에 연결합니다.
|
|
127
|
-
*/
|
|
128
222
|
private getWorkerDB(dbConfig: SonamuDBConfig): Knex {
|
|
129
|
-
// 트랜잭션이 있으면 트랜잭션 반환
|
|
130
223
|
if (this.testTransaction) {
|
|
131
224
|
return this.testTransaction;
|
|
132
225
|
}
|
|
133
226
|
|
|
134
227
|
const workerId = parseInt(process.env.VITEST_POOL_ID ?? "1", 10);
|
|
135
228
|
|
|
136
|
-
// Worker별 DB 인스턴스 캐싱
|
|
137
229
|
if (!this.workerDBs.has(workerId)) {
|
|
138
230
|
const baseTestConfig = dbConfig.test;
|
|
139
231
|
const connection = baseTestConfig.connection as { database: string };
|
|
@@ -158,29 +250,24 @@ export class DBClass {
|
|
|
158
250
|
|
|
159
251
|
getDBConfig(which: DBPreset): Knex.Config {
|
|
160
252
|
const dbConfig = this.getCurrentConfig();
|
|
161
|
-
|
|
253
|
+
|
|
254
|
+
if (isConcretePreset(which)) {
|
|
255
|
+
return dbConfig[which];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const environment = getSonamuEnvironment();
|
|
259
|
+
if (environment === "test") {
|
|
260
|
+
const target = which === "w" ? dbConfig.test : dbConfig.test_readonly;
|
|
162
261
|
return {
|
|
163
|
-
...
|
|
164
|
-
// 단일 풀
|
|
262
|
+
...target,
|
|
165
263
|
pool: {
|
|
166
264
|
min: 1,
|
|
167
265
|
max: 1,
|
|
168
266
|
},
|
|
169
267
|
};
|
|
170
268
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
case "staging":
|
|
174
|
-
return which === "w"
|
|
175
|
-
? dbConfig.development_master
|
|
176
|
-
: (dbConfig.development_slave ?? dbConfig.development_master);
|
|
177
|
-
case "production":
|
|
178
|
-
return which === "w"
|
|
179
|
-
? dbConfig.production_master
|
|
180
|
-
: (dbConfig.production_slave ?? dbConfig.production_master);
|
|
181
|
-
default:
|
|
182
|
-
throw new Error(`현재 ENV ${process.env.NODE_ENV}에는 설정 가능한 DB설정이 없습니다.`);
|
|
183
|
-
}
|
|
269
|
+
|
|
270
|
+
return which === "w" ? dbConfig[environment] : dbConfig[getReadonlyPreset(environment)];
|
|
184
271
|
}
|
|
185
272
|
|
|
186
273
|
async destroy(): Promise<void> {
|
|
@@ -192,17 +279,27 @@ export class DBClass {
|
|
|
192
279
|
await this.rdb.destroy();
|
|
193
280
|
this.rdb = undefined;
|
|
194
281
|
}
|
|
195
|
-
|
|
282
|
+
for (const db of this.presetDBs.values()) {
|
|
283
|
+
await db.destroy();
|
|
284
|
+
}
|
|
285
|
+
this.presetDBs.clear();
|
|
196
286
|
for (const db of this.workerDBs.values()) {
|
|
197
287
|
await db.destroy();
|
|
198
288
|
}
|
|
199
289
|
this.workerDBs.clear();
|
|
200
290
|
}
|
|
201
291
|
|
|
202
|
-
public generateDBConfig(
|
|
292
|
+
public generateDBConfig(
|
|
293
|
+
config: SonamuConfig["database"],
|
|
294
|
+
projectName?: string,
|
|
295
|
+
snapshots?: EnvironmentSnapshots,
|
|
296
|
+
): SonamuDBConfig {
|
|
297
|
+
assertNoLegacyDatabaseConfig(config);
|
|
298
|
+
|
|
299
|
+
const baseName = getProjectDatabaseBaseName(projectName);
|
|
203
300
|
const defaultKnexConfig = mergeConfigs<Partial<DatabaseConfig>>(
|
|
204
301
|
{
|
|
205
|
-
client: "postgresql",
|
|
302
|
+
client: config.database === "pgnative" ? "pgnative" : "postgresql",
|
|
206
303
|
pool: {
|
|
207
304
|
min: 1,
|
|
208
305
|
max: 5,
|
|
@@ -210,42 +307,67 @@ export class DBClass {
|
|
|
210
307
|
migrations: {
|
|
211
308
|
directory: "./src/migrations",
|
|
212
309
|
},
|
|
213
|
-
connection: {
|
|
214
|
-
database: config.name,
|
|
215
|
-
},
|
|
216
310
|
},
|
|
217
311
|
config.defaultOptions,
|
|
218
312
|
);
|
|
313
|
+
const baseConnection = defaultKnexConfig.connection as Knex.PgConnectionConfig | undefined;
|
|
314
|
+
const environmentFallbackConnection = snapshots
|
|
315
|
+
? neutralizeEnvironmentConnectionFields(baseConnection)
|
|
316
|
+
: baseConnection;
|
|
317
|
+
|
|
318
|
+
const envForPreset = (preset: SonamuMainDBPreset): NodeJS.ProcessEnv => {
|
|
319
|
+
if (!snapshots) {
|
|
320
|
+
return process.env;
|
|
321
|
+
}
|
|
322
|
+
if (preset === "fixture") {
|
|
323
|
+
return snapshots.test;
|
|
324
|
+
}
|
|
325
|
+
return snapshots[preset];
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const mainConfig = (preset: SonamuMainDBPreset): Knex.Config =>
|
|
329
|
+
mergeConfigs(defaultKnexConfig, {
|
|
330
|
+
connection: connectionFromEnv({
|
|
331
|
+
baseName,
|
|
332
|
+
suffix: preset,
|
|
333
|
+
baseConnection: environmentFallbackConnection,
|
|
334
|
+
env: envForPreset(preset),
|
|
335
|
+
}),
|
|
336
|
+
});
|
|
337
|
+
const readonlyConfig = (environment: SonamuEnvironment): Knex.Config =>
|
|
338
|
+
mergeConfigs<Knex.Config>(defaultKnexConfig, mainConfig(environment), {
|
|
339
|
+
connection: connectionFromEnv({
|
|
340
|
+
baseName,
|
|
341
|
+
suffix: environment,
|
|
342
|
+
baseConnection: mainConfig(environment).connection as Knex.PgConnectionConfig,
|
|
343
|
+
prefix: "SONAMU_DB_READONLY",
|
|
344
|
+
env: envForPreset(environment),
|
|
345
|
+
}),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const test = mainConfig("test");
|
|
219
349
|
|
|
220
|
-
// oxfmt-ignore -- 설정 구조 가독성을 위해 여러 줄로 유지
|
|
221
350
|
return {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
),
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
),
|
|
239
|
-
production_master: mergeConfigs(defaultKnexConfig, config.environments?.production),
|
|
240
|
-
production_slave: mergeConfigs(
|
|
241
|
-
defaultKnexConfig,
|
|
242
|
-
config.environments?.production,
|
|
243
|
-
config.environments?.production_slave,
|
|
244
|
-
),
|
|
351
|
+
test,
|
|
352
|
+
test_readonly: readonlyConfig("test"),
|
|
353
|
+
fixture: mergeConfigs(defaultKnexConfig, {
|
|
354
|
+
connection: connectionFromEnv({
|
|
355
|
+
baseName,
|
|
356
|
+
suffix: "fixture",
|
|
357
|
+
baseConnection: environmentFallbackConnection,
|
|
358
|
+
prefix: "SONAMU_DB_FIXTURE",
|
|
359
|
+
env: envForPreset("fixture"),
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
362
|
+
development: mainConfig("development"),
|
|
363
|
+
development_readonly: readonlyConfig("development"),
|
|
364
|
+
staging: mainConfig("staging"),
|
|
365
|
+
staging_readonly: readonlyConfig("staging"),
|
|
366
|
+
production: mainConfig("production"),
|
|
367
|
+
production_readonly: readonlyConfig("production"),
|
|
245
368
|
};
|
|
246
369
|
}
|
|
247
370
|
|
|
248
|
-
// Test 환경에서 트랜잭션 사용
|
|
249
371
|
public testTransaction: Knex.Transaction | null = null;
|
|
250
372
|
async createTestTransaction(): Promise<Knex.Transaction> {
|
|
251
373
|
const db = this.getDB("w");
|
package/src/dict/en.ts
CHANGED
|
@@ -80,6 +80,8 @@ export default {
|
|
|
80
80
|
"sonamu.error.keyNotFound": (key: string) => `Key not found: ${key}`,
|
|
81
81
|
"sonamu.error.migrationRejected": "Migration has been rejected",
|
|
82
82
|
"sonamu.error.slackConfirmNotConfigured": "Slack Confirm is not configured",
|
|
83
|
+
"sonamu.error.slackConfirmInvalidTargets": (targets: string, validTargets: string) =>
|
|
84
|
+
`Slack Confirm targets include unknown DB keys: ${targets}. Valid targets: ${validTargets}`,
|
|
83
85
|
"sonamu.error.devRunner.notEnabled":
|
|
84
86
|
"devRunner is not enabled. Set test.devRunner.enabled: true in sonamu.config.ts",
|
|
85
87
|
"sonamu.error.devRunner.notReady": "Vitest instance is not ready yet",
|
package/src/dict/ko.ts
CHANGED
|
@@ -81,6 +81,8 @@ export default {
|
|
|
81
81
|
"sonamu.error.keyNotFound": (key: string) => `키를 찾을 수 없습니다: ${key}`,
|
|
82
82
|
"sonamu.error.migrationRejected": "마이그레이션이 거절되었습니다",
|
|
83
83
|
"sonamu.error.slackConfirmNotConfigured": "Slack Confirm이 설정되지 않았습니다",
|
|
84
|
+
"sonamu.error.slackConfirmInvalidTargets": (targets: string, validTargets: string) =>
|
|
85
|
+
`Slack Confirm targets에 알 수 없는 DB 키가 포함되어 있습니다: ${targets}. 유효한 대상: ${validTargets}`,
|
|
84
86
|
"sonamu.error.devRunner.notEnabled":
|
|
85
87
|
"devRunner가 활성화되지 않았습니다. sonamu.config.ts에서 test.devRunner.enabled: true 설정이 필요합니다",
|
|
86
88
|
"sonamu.error.devRunner.notReady": "Vitest 인스턴스가 아직 준비되지 않았습니다",
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
|
|
6
|
+
export const SONAMU_ENVIRONMENTS = ["test", "development", "staging", "production"] as const;
|
|
7
|
+
|
|
8
|
+
export type SonamuEnvironment = (typeof SONAMU_ENVIRONMENTS)[number];
|
|
9
|
+
export type EnvironmentSnapshot = Record<string, string>;
|
|
10
|
+
export type EnvironmentSnapshots = Record<SonamuEnvironment, EnvironmentSnapshot>;
|
|
11
|
+
|
|
12
|
+
export function isSonamuEnvironment(value: string | undefined): value is SonamuEnvironment {
|
|
13
|
+
return SONAMU_ENVIRONMENTS.includes(value as SonamuEnvironment);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getSonamuEnvironment(env: NodeJS.ProcessEnv = process.env): SonamuEnvironment {
|
|
17
|
+
const nodeEnv = env.NODE_ENV;
|
|
18
|
+
|
|
19
|
+
if (nodeEnv === undefined || nodeEnv === "") {
|
|
20
|
+
return "development";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isSonamuEnvironment(nodeEnv)) {
|
|
24
|
+
return nodeEnv;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Invalid NODE_ENV "${nodeEnv}". Sonamu supports only ${SONAMU_ENVIRONMENTS.join(", ")}.`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readDotenvFile(filePath: string): EnvironmentSnapshot {
|
|
33
|
+
if (!existsSync(filePath)) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return dotenv.parse(readFileSync(filePath));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertEnvironmentDotenvExists(rootPath: string, environment: SonamuEnvironment): void {
|
|
41
|
+
const commonEnvPath = path.join(rootPath, ".env");
|
|
42
|
+
const environmentEnvPath = path.join(rootPath, `.env.${environment}`);
|
|
43
|
+
|
|
44
|
+
if (!existsSync(commonEnvPath) && !existsSync(environmentEnvPath)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Missing Sonamu dotenv file. Create ${commonEnvPath} or ${environmentEnvPath}.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removePreloadedCommonDotenvValues(
|
|
52
|
+
baseEnv: NodeJS.ProcessEnv,
|
|
53
|
+
commonEnv: EnvironmentSnapshot,
|
|
54
|
+
environmentEnv: EnvironmentSnapshot,
|
|
55
|
+
): NodeJS.ProcessEnv {
|
|
56
|
+
const runtimeEnv = { ...baseEnv };
|
|
57
|
+
|
|
58
|
+
for (const [key, commonValue] of Object.entries(commonEnv)) {
|
|
59
|
+
if (environmentEnv[key] !== undefined && runtimeEnv[key] === commonValue) {
|
|
60
|
+
delete runtimeEnv[key];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return runtimeEnv;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readEnvironmentSnapshot(
|
|
68
|
+
rootPath: string,
|
|
69
|
+
environment: SonamuEnvironment,
|
|
70
|
+
baseEnv: NodeJS.ProcessEnv = process.env,
|
|
71
|
+
): EnvironmentSnapshot {
|
|
72
|
+
assertEnvironmentDotenvExists(rootPath, environment);
|
|
73
|
+
const commonEnv = readDotenvFile(path.join(rootPath, ".env"));
|
|
74
|
+
const environmentEnv = readDotenvFile(path.join(rootPath, `.env.${environment}`));
|
|
75
|
+
const runtimeEnv = removePreloadedCommonDotenvValues(baseEnv, commonEnv, environmentEnv);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
...commonEnv,
|
|
79
|
+
...environmentEnv,
|
|
80
|
+
...readDotenvFile(path.join(rootPath, ".env.local")),
|
|
81
|
+
...runtimeEnv,
|
|
82
|
+
NODE_ENV: environment,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function readAllEnvironmentSnapshots(
|
|
87
|
+
rootPath: string,
|
|
88
|
+
baseEnv: NodeJS.ProcessEnv = {},
|
|
89
|
+
): EnvironmentSnapshots {
|
|
90
|
+
return Object.fromEntries(
|
|
91
|
+
SONAMU_ENVIRONMENTS.map((environment) => [
|
|
92
|
+
environment,
|
|
93
|
+
readEnvironmentSnapshot(rootPath, environment, baseEnv),
|
|
94
|
+
]),
|
|
95
|
+
) as EnvironmentSnapshots;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function applyCurrentSnapshotToProcessEnv(rootPath: string): EnvironmentSnapshot {
|
|
99
|
+
const environment = getSonamuEnvironment();
|
|
100
|
+
const snapshot = readEnvironmentSnapshot(rootPath, environment);
|
|
101
|
+
|
|
102
|
+
for (const [key, value] of Object.entries(snapshot)) {
|
|
103
|
+
process.env[key] = value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return snapshot;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isDevelopmentEnvironment(): boolean {
|
|
110
|
+
return getSonamuEnvironment() === "development";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function isStagingEnvironment(): boolean {
|
|
114
|
+
return getSonamuEnvironment() === "staging";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isProductionEnvironment(): boolean {
|
|
118
|
+
return getSonamuEnvironment() === "production";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isTestEnvironment(): boolean {
|
|
122
|
+
return getSonamuEnvironment() === "test";
|
|
123
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { type SonamuConfig } from "../../api/config";
|
|
4
|
+
import { Sonamu } from "../../api/sonamu";
|
|
5
|
+
import { type SonamuDBConfig, type SonamuDBPreset } from "../../database/db";
|
|
6
|
+
import { setSDConfig } from "../../dict/sd";
|
|
7
|
+
import { Migrator } from "../migrator";
|
|
8
|
+
import { SlackConfirm } from "../slack-confirm";
|
|
9
|
+
|
|
10
|
+
describe("Migrator environment target filtering", () => {
|
|
11
|
+
const originalEnv = { ...process.env };
|
|
12
|
+
const presets: SonamuDBPreset[] = [
|
|
13
|
+
"test",
|
|
14
|
+
"fixture",
|
|
15
|
+
"development",
|
|
16
|
+
"staging",
|
|
17
|
+
"production",
|
|
18
|
+
"test_readonly",
|
|
19
|
+
"development_readonly",
|
|
20
|
+
"staging_readonly",
|
|
21
|
+
"production_readonly",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const dbConfig = Object.fromEntries(
|
|
25
|
+
presets.map((preset) => [
|
|
26
|
+
preset,
|
|
27
|
+
{
|
|
28
|
+
client: "postgresql",
|
|
29
|
+
connection: {
|
|
30
|
+
host: "localhost",
|
|
31
|
+
database: preset,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
]),
|
|
35
|
+
) as SonamuDBConfig;
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
for (const key of Object.keys(process.env)) {
|
|
39
|
+
if (!(key in originalEnv)) {
|
|
40
|
+
delete process.env[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
Object.assign(process.env, originalEnv);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const getMigrationTargetKeys = () => {
|
|
47
|
+
Sonamu.dbConfig = dbConfig;
|
|
48
|
+
return (
|
|
49
|
+
new Migrator() as unknown as {
|
|
50
|
+
getMigrationTargetKeys(): (keyof SonamuDBConfig)[];
|
|
51
|
+
}
|
|
52
|
+
).getMigrationTargetKeys();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
it("limits migration targets to production on a production server runtime", () => {
|
|
56
|
+
process.env.NODE_ENV = "production";
|
|
57
|
+
|
|
58
|
+
expect(getMigrationTargetKeys()).toEqual(["production"]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("keeps all writable targets available for local development UI workflows", () => {
|
|
62
|
+
process.env.NODE_ENV = "development";
|
|
63
|
+
|
|
64
|
+
expect(getMigrationTargetKeys()).toEqual([
|
|
65
|
+
"test",
|
|
66
|
+
"fixture",
|
|
67
|
+
"development",
|
|
68
|
+
"staging",
|
|
69
|
+
"production",
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("SlackConfirm target validation", () => {
|
|
75
|
+
const presets: SonamuDBPreset[] = [
|
|
76
|
+
"test",
|
|
77
|
+
"fixture",
|
|
78
|
+
"development",
|
|
79
|
+
"staging",
|
|
80
|
+
"production",
|
|
81
|
+
"test_readonly",
|
|
82
|
+
"development_readonly",
|
|
83
|
+
"staging_readonly",
|
|
84
|
+
"production_readonly",
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const dbConfig = Object.fromEntries(
|
|
88
|
+
presets.map((preset) => [
|
|
89
|
+
preset,
|
|
90
|
+
{
|
|
91
|
+
client: "postgresql",
|
|
92
|
+
connection: {
|
|
93
|
+
host: "localhost",
|
|
94
|
+
database: preset,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
]),
|
|
98
|
+
) as SonamuDBConfig;
|
|
99
|
+
|
|
100
|
+
const createConfig = (targets: string[]): SonamuConfig =>
|
|
101
|
+
({
|
|
102
|
+
projectName: "sonamu-test",
|
|
103
|
+
api: {
|
|
104
|
+
dir: "src",
|
|
105
|
+
route: {
|
|
106
|
+
prefix: "/api",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
i18n: {
|
|
110
|
+
defaultLocale: "ko",
|
|
111
|
+
supportedLocales: ["ko", "en"],
|
|
112
|
+
},
|
|
113
|
+
sync: {
|
|
114
|
+
targets: [],
|
|
115
|
+
},
|
|
116
|
+
database: {},
|
|
117
|
+
server: {},
|
|
118
|
+
apiConfig: {
|
|
119
|
+
contextProvider: (defaultContext) => defaultContext,
|
|
120
|
+
guardHandler: () => undefined,
|
|
121
|
+
},
|
|
122
|
+
slackConfirm: {
|
|
123
|
+
targets,
|
|
124
|
+
botToken: "xoxb-test",
|
|
125
|
+
channelId: "C123",
|
|
126
|
+
},
|
|
127
|
+
}) as SonamuConfig;
|
|
128
|
+
|
|
129
|
+
it("fails fast when slackConfirm.targets contains an unknown DB key", () => {
|
|
130
|
+
Sonamu.dbConfig = dbConfig;
|
|
131
|
+
Sonamu.config = createConfig(["production_old"]);
|
|
132
|
+
setSDConfig(Sonamu.config.i18n);
|
|
133
|
+
|
|
134
|
+
expect(() => new SlackConfirm().isTargetRequiresApproval("production")).toThrow(
|
|
135
|
+
/Slack Confirm targets/,
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("checks approval requirements only after configured targets match dbConfig keys", () => {
|
|
140
|
+
Sonamu.dbConfig = dbConfig;
|
|
141
|
+
Sonamu.config = createConfig(["production"]);
|
|
142
|
+
setSDConfig(Sonamu.config.i18n);
|
|
143
|
+
|
|
144
|
+
const slackConfirm = new SlackConfirm();
|
|
145
|
+
|
|
146
|
+
expect(slackConfirm.isTargetRequiresApproval("production")).toBe(true);
|
|
147
|
+
expect(slackConfirm.isTargetRequiresApproval("staging")).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|