cabloy 5.1.220 → 5.1.222
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/.cabloy-version +1 -1
- package/.github/workflows/agent-governance.yml +2 -2
- package/.github/workflows/create-cabloy.yml +2 -2
- package/.github/workflows/docs-pages.yml +2 -2
- package/.github/workflows/playwright-e2e.yml +4 -4
- package/.github/workflows/vona-cov-pg.yml +2 -2
- package/.github/workflows/vona-test-crud.yml +2 -2
- package/.github/workflows/vona-test-mysql.yml +2 -2
- package/.github/workflows/vona-test-pg.yml +2 -2
- package/.github/workflows/vona-test-sqlite3.yml +2 -2
- package/.github/workflows/vona-tsc.yml +2 -2
- package/.github/workflows/zova-ui.yml +2 -2
- package/CHANGELOG.md +22 -0
- package/package.json +4 -3
- package/repo-agent-governance/managed-assets.json +15 -15
- package/repo-agent-governance/policies/repository.md +1 -0
- package/repo-agent-governance/skills/cabloy-backend-scaffold/SKILL.md +5 -1
- package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +7 -5
- package/repo-agent-governance/skills/cabloy-frontend-scaffold/SKILL.md +5 -1
- package/repo-agent-governance/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +6 -4
- package/repo-docs/backend/unit-testing.md +5 -1
- package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
- package/repo-docs/frontend/scripts.md +8 -0
- package/repo-docs/reference/repo-scripts.md +5 -1
- package/scripts/generateOpenapiIsolated.ts +18 -1
- package/scripts/runIsolatedTest.test.ts +9 -1
- package/scripts/runIsolatedTest.ts +81 -18
- package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
- package/scripts/strictGenerationDiagnostics.ts +61 -0
- package/vona/package.original.json +3 -2
- package/vona/packages-cli/cabloy-cli/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
- package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
- package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
- package/vona/packages-utils/process-helper/package.json +1 -1
- package/vona/packages-utils/process-helper/src/index.test.ts +62 -0
- package/vona/packages-utils/process-helper/src/index.ts +9 -4
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.test.ts +558 -0
- package/vona/packages-vona/vona-core/src/lib/core/completedReadiness.ts +389 -0
- package/vona/packages-vona/vona-core/src/lib/core/context.ts +3 -0
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +11 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +10 -2
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +268 -68
- package/vona/packages-vona/vona-core/src/lib/core/meta.ts +5 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +3 -9
- package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +10 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +8 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +542 -236
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +124 -4
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +7 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExisting.test.ts +423 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/test/readyExistingCommit.test.ts +174 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/bean/bean.instance.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/lib/existingReadyAdmission.ts +125 -0
- package/vona/src/suite-vendor/a-vona/modules/a-instance/src/service/instance.ts +72 -25
- package/vona/src/suite-vendor/a-vona/modules/a-instance/test/existingReadyAdmission.test.ts +949 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +61 -59
- package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-mail/src/service/mail.ts +5 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +61 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +3 -3
- package/zova/packages-cli/cli/package.json +4 -4
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +22 -1
- package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +137 -0
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +448 -641
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { test } from 'node:test';
|
|
2
|
+
import { afterEach, beforeEach, test } from 'node:test';
|
|
3
3
|
import * as Winston from 'winston';
|
|
4
4
|
import Transport from 'winston-transport';
|
|
5
5
|
|
|
6
6
|
import { createRequestBoundary, runWithRequestBoundary } from '../requestBoundary.ts';
|
|
7
|
+
import { requiresFreshTestResources, TEST_REQUIRE_FRESH_RESOURCES_ENV } from '../testStrictMode.ts';
|
|
7
8
|
import { AppLogger } from './appLogger.ts';
|
|
9
|
+
import { getSuppressedDiagnosticLogger } from './diagnosticLogger.ts';
|
|
10
|
+
|
|
11
|
+
let previousStrictFlag: string | undefined;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
previousStrictFlag = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
14
|
+
delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (previousStrictFlag === undefined) delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
18
|
+
else process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = previousStrictFlag;
|
|
19
|
+
});
|
|
8
20
|
|
|
9
21
|
class MemoryTransport extends Transport {
|
|
10
22
|
count = 0;
|
|
@@ -58,87 +70,275 @@ function payloadProbe() {
|
|
|
58
70
|
};
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
function throwingProbe() {
|
|
74
|
+
let reads = 0;
|
|
75
|
+
const read = () => {
|
|
76
|
+
reads++;
|
|
77
|
+
throw new Error('Synthetic logger input must not be evaluated');
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
value: new Proxy({}, { get: read, ownKeys: read }),
|
|
81
|
+
read,
|
|
82
|
+
reads: () => reads,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
test(
|
|
87
|
+
'cached diagnostic handlers never expose raw Winston to a request boundary',
|
|
88
|
+
{ concurrency: false },
|
|
89
|
+
async () => {
|
|
90
|
+
const local = fixture();
|
|
91
|
+
const { raw, logger, sink } = local;
|
|
92
|
+
const exceptions = logger.exceptions;
|
|
93
|
+
const rejections = logger.rejections;
|
|
94
|
+
const probe = payloadProbe();
|
|
95
|
+
try {
|
|
96
|
+
assert.equal(local.factory.get(), logger);
|
|
97
|
+
assert.equal(local.creations(), 1);
|
|
98
|
+
assert.equal(logger.exceptions, exceptions);
|
|
99
|
+
assert.equal(logger.rejections, rejections);
|
|
100
|
+
for (const handler of [exceptions, rejections]) {
|
|
101
|
+
assert.equal(handler.logger, logger);
|
|
102
|
+
assert.equal(Reflect.set(handler, 'logger', raw), false);
|
|
103
|
+
}
|
|
104
|
+
assert.equal(raw.exceptions.logger, raw);
|
|
105
|
+
assert.equal(raw.rejections.logger, raw);
|
|
106
|
+
runWithRequestBoundary(createRequestBoundary(), () => {
|
|
107
|
+
assert.equal(local.factory.get(), getSuppressedDiagnosticLogger());
|
|
108
|
+
assert.equal(local.factory.get('unconfigured' as any), getSuppressedDiagnosticLogger());
|
|
109
|
+
assert.equal(local.factory.child('synthetic' as any), getSuppressedDiagnosticLogger());
|
|
110
|
+
logger.error(probe.payload);
|
|
111
|
+
logger.exceptions.logger.error(probe.payload);
|
|
112
|
+
logger.rejections.logger.error(probe.payload);
|
|
113
|
+
exceptions.logger.error(probe.payload);
|
|
114
|
+
rejections.logger.error(probe.payload);
|
|
115
|
+
});
|
|
116
|
+
assert.equal(probe.reads(), 0);
|
|
117
|
+
assert.equal(sink.count, 0);
|
|
118
|
+
for (const handler of [exceptions, rejections]) {
|
|
119
|
+
const before = probe.reads();
|
|
120
|
+
handler.logger.error(probe.payload);
|
|
121
|
+
assert.ok(probe.reads() > before);
|
|
122
|
+
}
|
|
123
|
+
assert.equal(sink.count, 2);
|
|
124
|
+
const before = probe.reads();
|
|
125
|
+
raw.error(probe.payload);
|
|
126
|
+
assert.ok(probe.reads() > before);
|
|
127
|
+
assert.equal(sink.count, 3);
|
|
128
|
+
} finally {
|
|
129
|
+
await local.close();
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
test(
|
|
135
|
+
'diagnostic handler methods keep their raw receiver and map fluent returns',
|
|
136
|
+
{ concurrency: false },
|
|
137
|
+
async () => {
|
|
138
|
+
const local = fixture();
|
|
139
|
+
try {
|
|
140
|
+
for (const key of ['exceptions', 'rejections'] as const) {
|
|
141
|
+
const rawHandler = local.raw[key];
|
|
142
|
+
const handler = local.logger[key];
|
|
143
|
+
const marker = {};
|
|
144
|
+
const original = rawHandler.getAllInfo;
|
|
145
|
+
Object.defineProperty(rawHandler, 'getAllInfo', {
|
|
146
|
+
configurable: true,
|
|
147
|
+
value(this: typeof rawHandler, value: unknown) {
|
|
148
|
+
assert.equal(this, rawHandler);
|
|
149
|
+
assert.equal(value, marker);
|
|
150
|
+
return this;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
assert.equal(handler.handlers, rawHandler.handlers);
|
|
155
|
+
assert.equal(handler.getAllInfo(marker as any), handler);
|
|
156
|
+
assert.equal(handler.logger, local.logger);
|
|
157
|
+
} finally {
|
|
158
|
+
Object.defineProperty(rawHandler, 'getAllInfo', {
|
|
159
|
+
configurable: true,
|
|
160
|
+
value: original,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const exceptions = local.logger.exceptions;
|
|
165
|
+
const rejections = local.logger.rejections;
|
|
166
|
+
local.logger.configure({ transports: [local.sink] });
|
|
167
|
+
assert.notEqual(local.logger.exceptions, exceptions);
|
|
168
|
+
assert.notEqual(local.logger.rejections, rejections);
|
|
169
|
+
assert.equal(local.logger.exceptions.logger, local.logger);
|
|
170
|
+
assert.equal(local.logger.rejections.logger, local.logger);
|
|
171
|
+
assert.equal(exceptions.logger, local.logger);
|
|
172
|
+
assert.equal(rejections.logger, local.logger);
|
|
173
|
+
runWithRequestBoundary(createRequestBoundary(), () => {
|
|
174
|
+
exceptions.logger.error('local');
|
|
175
|
+
rejections.logger.error('local');
|
|
176
|
+
});
|
|
177
|
+
assert.equal(local.sink.count, 0);
|
|
178
|
+
} finally {
|
|
179
|
+
await local.close();
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
test('strict logger bypasses cached clients and payloads', { concurrency: false }, async () => {
|
|
62
185
|
const local = fixture();
|
|
63
|
-
const
|
|
64
|
-
const exceptions = logger.exceptions;
|
|
65
|
-
const rejections = logger.rejections;
|
|
66
|
-
const probe = payloadProbe();
|
|
186
|
+
const probe = throwingProbe();
|
|
67
187
|
try {
|
|
68
|
-
|
|
188
|
+
process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
|
|
189
|
+
const suppressed = getSuppressedDiagnosticLogger();
|
|
190
|
+
assert.equal(local.factory.get(), suppressed);
|
|
191
|
+
assert.equal(local.factory.get('unconfigured' as any), suppressed);
|
|
192
|
+
assert.equal(local.factory.child('synthetic' as any), suppressed);
|
|
193
|
+
assert.equal(local.factory.child('synthetic' as any, 'unconfigured' as any), suppressed);
|
|
194
|
+
assert.notEqual(suppressed, local.logger);
|
|
195
|
+
assert.equal(suppressed.error(probe.value), suppressed);
|
|
196
|
+
assert.equal(suppressed.info(probe.value), suppressed);
|
|
197
|
+
assert.equal(suppressed.log(probe.value as any), suppressed);
|
|
198
|
+
assert.equal(suppressed.child(probe.value), suppressed);
|
|
199
|
+
assert.equal(suppressed.startTimer().done(probe.value), suppressed);
|
|
200
|
+
let completions = 0;
|
|
201
|
+
assert.equal(
|
|
202
|
+
suppressed.write(probe.value, () => completions++),
|
|
203
|
+
true,
|
|
204
|
+
);
|
|
205
|
+
assert.equal(completions, 1);
|
|
206
|
+
assert.equal(probe.reads(), 0);
|
|
69
207
|
assert.equal(local.creations(), 1);
|
|
70
|
-
assert.equal(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
208
|
+
assert.equal(local.sink.count, 0);
|
|
209
|
+
delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
210
|
+
assert.equal(local.factory.get(), local.logger);
|
|
211
|
+
local.factory.child().info('ordinary logging remains available');
|
|
212
|
+
assert.equal(local.sink.count, 1);
|
|
213
|
+
} finally {
|
|
214
|
+
await local.close();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('strict entry guards precede cache and app access', { concurrency: false }, async () => {
|
|
219
|
+
const local = fixture();
|
|
220
|
+
const probe = throwingProbe();
|
|
221
|
+
const descriptors = Object.getOwnPropertyDescriptors(local.factory);
|
|
222
|
+
const cacheKeys = Object.getOwnPropertySymbols(local.factory);
|
|
223
|
+
try {
|
|
224
|
+
assert.equal(cacheKeys.length, 2);
|
|
225
|
+
for (const key of cacheKeys) {
|
|
226
|
+
Object.defineProperty(local.factory, key, { configurable: true, get: probe.read });
|
|
75
227
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
228
|
+
Object.defineProperty(local.factory, 'app', { configurable: true, get: probe.read });
|
|
229
|
+
process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
|
|
230
|
+
assert.equal(local.factory.get(), getSuppressedDiagnosticLogger());
|
|
231
|
+
assert.equal(local.factory.get(probe.value as any), getSuppressedDiagnosticLogger());
|
|
232
|
+
Object.defineProperty(local.factory, 'get', { configurable: true, get: probe.read });
|
|
233
|
+
assert.equal(local.factory.child(), getSuppressedDiagnosticLogger());
|
|
234
|
+
assert.equal(
|
|
235
|
+
local.factory.child(probe.value as any, probe.value as any),
|
|
236
|
+
getSuppressedDiagnosticLogger(),
|
|
237
|
+
);
|
|
238
|
+
assert.equal(local.creations(), 1);
|
|
85
239
|
assert.equal(probe.reads(), 0);
|
|
86
|
-
assert.equal(sink.count, 0);
|
|
87
|
-
for (const handler of [exceptions, rejections]) {
|
|
88
|
-
const before = probe.reads();
|
|
89
|
-
handler.logger.error(probe.payload);
|
|
90
|
-
assert.ok(probe.reads() > before);
|
|
91
|
-
}
|
|
92
|
-
assert.equal(sink.count, 2);
|
|
93
|
-
const before = probe.reads();
|
|
94
|
-
raw.error(probe.payload);
|
|
95
|
-
assert.ok(probe.reads() > before);
|
|
96
|
-
assert.equal(sink.count, 3);
|
|
240
|
+
assert.equal(local.sink.count, 0);
|
|
97
241
|
} finally {
|
|
242
|
+
delete (local.factory as any).get;
|
|
243
|
+
delete (local.factory as any).app;
|
|
244
|
+
Object.defineProperties(local.factory, descriptors);
|
|
98
245
|
await local.close();
|
|
99
246
|
}
|
|
100
247
|
});
|
|
101
248
|
|
|
102
|
-
test('
|
|
249
|
+
test('strict logger skips real configuration factories', { concurrency: false }, async () => {
|
|
250
|
+
const factory = new AppLogger();
|
|
251
|
+
const probe = throwingProbe();
|
|
252
|
+
let configurations = 0;
|
|
253
|
+
Object.defineProperty(factory, 'app', {
|
|
254
|
+
value: {
|
|
255
|
+
config: {
|
|
256
|
+
logger: {
|
|
257
|
+
base() {
|
|
258
|
+
configurations++;
|
|
259
|
+
return probe.read();
|
|
260
|
+
},
|
|
261
|
+
clients: {
|
|
262
|
+
default() {
|
|
263
|
+
configurations++;
|
|
264
|
+
return probe.read();
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
get baseDir() {
|
|
268
|
+
return probe.read();
|
|
269
|
+
},
|
|
270
|
+
get rotate() {
|
|
271
|
+
return probe.read();
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
try {
|
|
278
|
+
process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
|
|
279
|
+
assert.equal(factory.get(), getSuppressedDiagnosticLogger());
|
|
280
|
+
assert.equal(factory.child('synthetic' as any), getSuppressedDiagnosticLogger());
|
|
281
|
+
assert.equal(configurations, 0);
|
|
282
|
+
assert.equal(probe.reads(), 0);
|
|
283
|
+
} finally {
|
|
284
|
+
await factory.dispose();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('strict file transport fails before any input access', { concurrency: false }, () => {
|
|
289
|
+
const factory = new AppLogger();
|
|
290
|
+
const probe = throwingProbe();
|
|
291
|
+
const args = [probe.value, probe.value, probe.value] as unknown as Parameters<
|
|
292
|
+
AppLogger['createTransportFile']
|
|
293
|
+
>;
|
|
294
|
+
Object.defineProperty(factory, 'app', { get: probe.read });
|
|
295
|
+
process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
|
|
296
|
+
assert.throws(() => factory.createTransportFile(...args), {
|
|
297
|
+
message: 'Strict test file logger transport is disabled',
|
|
298
|
+
});
|
|
299
|
+
runWithRequestBoundary(createRequestBoundary(), () => {
|
|
300
|
+
assert.throws(() => factory.createTransportFile(...args), {
|
|
301
|
+
message: 'Strict test file logger transport is disabled',
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
assert.equal(probe.reads(), 0);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('invalid strict flags never fall back to loggers', { concurrency: false }, async () => {
|
|
103
308
|
const local = fixture();
|
|
309
|
+
const probe = throwingProbe();
|
|
310
|
+
Object.defineProperty(local.factory, 'app', { get: probe.read });
|
|
311
|
+
const args = [probe.value, probe.value, probe.value] as unknown as Parameters<
|
|
312
|
+
AppLogger['createTransportFile']
|
|
313
|
+
>;
|
|
104
314
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
315
|
+
const env: NodeJS.ProcessEnv = { NODE_ENV: 'test', META_FLAVOR: 'normal', META_MODE: 'test' };
|
|
316
|
+
assert.equal(requiresFreshTestResources(), false);
|
|
317
|
+
assert.equal(requiresFreshTestResources(env), false);
|
|
318
|
+
assert.equal(
|
|
319
|
+
requiresFreshTestResources({ ...env, [TEST_REQUIRE_FRESH_RESOURCES_ENV]: 'true' }),
|
|
320
|
+
true,
|
|
321
|
+
);
|
|
322
|
+
for (const flag of ['', 'false', 'TRUE', 'synthetic-invalid-flag']) {
|
|
323
|
+
process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = flag;
|
|
324
|
+
const expected = { message: 'Invalid strict test resource freshness flag' };
|
|
325
|
+
assert.throws(() => requiresFreshTestResources(), expected);
|
|
326
|
+
assert.throws(
|
|
327
|
+
() => requiresFreshTestResources({ ...env, [TEST_REQUIRE_FRESH_RESOURCES_ENV]: flag }),
|
|
328
|
+
expected,
|
|
329
|
+
);
|
|
330
|
+
const calls = [
|
|
331
|
+
() => local.factory.get(),
|
|
332
|
+
() => local.factory.child('synthetic' as any),
|
|
333
|
+
() => local.factory.createTransportFile(...args),
|
|
334
|
+
];
|
|
335
|
+
for (const call of calls) {
|
|
336
|
+
assert.throws(call, expected);
|
|
337
|
+
runWithRequestBoundary(createRequestBoundary(), () => assert.throws(call, expected));
|
|
127
338
|
}
|
|
128
339
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
local.logger.configure({ transports: [local.sink] });
|
|
132
|
-
assert.notEqual(local.logger.exceptions, exceptions);
|
|
133
|
-
assert.notEqual(local.logger.rejections, rejections);
|
|
134
|
-
assert.equal(local.logger.exceptions.logger, local.logger);
|
|
135
|
-
assert.equal(local.logger.rejections.logger, local.logger);
|
|
136
|
-
assert.equal(exceptions.logger, local.logger);
|
|
137
|
-
assert.equal(rejections.logger, local.logger);
|
|
138
|
-
runWithRequestBoundary(createRequestBoundary(), () => {
|
|
139
|
-
exceptions.logger.error('local');
|
|
140
|
-
rejections.logger.error('local');
|
|
141
|
-
});
|
|
340
|
+
assert.equal(probe.reads(), 0);
|
|
341
|
+
assert.equal(local.creations(), 1);
|
|
142
342
|
assert.equal(local.sink.count, 0);
|
|
143
343
|
} finally {
|
|
144
344
|
await local.close();
|
|
@@ -15,6 +15,7 @@ import type { AppResource } from './resource.ts';
|
|
|
15
15
|
import { EnumAppEvent } from '../../types/index.ts';
|
|
16
16
|
import { BeanSimple } from '../bean/beanSimple.ts';
|
|
17
17
|
import { AppLocale, BeanScopeContainer } from '../bean/index.ts';
|
|
18
|
+
import { closeCompletedReadiness, createCompletedReadiness } from './completedReadiness.ts';
|
|
18
19
|
import { CtxCounter } from './ctxCounter.ts';
|
|
19
20
|
import { AppHmr } from './hmr.ts';
|
|
20
21
|
import { AppLogger } from './logger/appLogger.ts';
|
|
@@ -51,6 +52,8 @@ export class AppMeta extends BeanSimple {
|
|
|
51
52
|
hmrCacheLocaleModules: TypeModuleResourceLocaleModules;
|
|
52
53
|
hmrCacheConfigModules: IBeanScopeConfig;
|
|
53
54
|
//
|
|
55
|
+
completedReadiness: ReturnType<typeof createCompletedReadiness>;
|
|
56
|
+
|
|
54
57
|
appReady: boolean;
|
|
55
58
|
appReadyInstances: Record<string, boolean>;
|
|
56
59
|
//
|
|
@@ -61,6 +64,7 @@ export class AppMeta extends BeanSimple {
|
|
|
61
64
|
appClosed: boolean;
|
|
62
65
|
|
|
63
66
|
protected __init__(env: VonaConfigEnv) {
|
|
67
|
+
this.completedReadiness = createCompletedReadiness(this.app);
|
|
64
68
|
// env
|
|
65
69
|
this.env = env;
|
|
66
70
|
this._prepareEnv();
|
|
@@ -121,6 +125,7 @@ export class AppMeta extends BeanSimple {
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
async close() {
|
|
128
|
+
closeCompletedReadiness(this.completedReadiness);
|
|
124
129
|
if (!this[SymbolClosePromise]) {
|
|
125
130
|
this[SymbolClosePromise] = this._closeInner();
|
|
126
131
|
}
|
|
@@ -12,17 +12,11 @@ import {
|
|
|
12
12
|
mergeTestClaimRedisOptions,
|
|
13
13
|
} from './testClaim.ts';
|
|
14
14
|
import { resolveVonaLocalPaths } from './testIsolation.ts';
|
|
15
|
+
import { requiresFreshTestResources } from './testStrictMode.ts';
|
|
15
16
|
|
|
16
|
-
export
|
|
17
|
-
const freshApps = new WeakSet<VonaApplication>();
|
|
17
|
+
export { requiresFreshTestResources, TEST_REQUIRE_FRESH_RESOURCES_ENV } from './testStrictMode.ts';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const value = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
21
|
-
if (value !== undefined && value !== 'true') {
|
|
22
|
-
throw new Error('Invalid strict test resource freshness flag');
|
|
23
|
-
}
|
|
24
|
-
return value === 'true';
|
|
25
|
-
}
|
|
19
|
+
const freshApps = new WeakSet<VonaApplication>();
|
|
26
20
|
|
|
27
21
|
export function assertFreshTestResourcesReady(app: VonaApplication) {
|
|
28
22
|
if (!requiresFreshTestResources()) return;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const TEST_REQUIRE_FRESH_RESOURCES_ENV = 'VONA_TEST_REQUIRE_FRESH_RESOURCES';
|
|
2
|
+
|
|
3
|
+
export function requiresFreshTestResources(env: NodeJS.ProcessEnv = process.env) {
|
|
4
|
+
const value = env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
5
|
+
if (value !== undefined && value !== 'true') {
|
|
6
|
+
throw new Error('Invalid strict test resource freshness flag');
|
|
7
|
+
}
|
|
8
|
+
return value === 'true';
|
|
9
|
+
}
|
|
@@ -31,6 +31,7 @@ const lifecycleSource = `
|
|
|
31
31
|
return nextResolve(specifier, context);
|
|
32
32
|
},
|
|
33
33
|
});
|
|
34
|
+
const { createCompletedReadiness } = await import(new URL('../core/completedReadiness.ts', startUrl).href);
|
|
34
35
|
const { Start } = await import(startUrl);
|
|
35
36
|
const [projectPath, lockPath, lockContents, failingPhase] = process.argv.slice(1);
|
|
36
37
|
const phases = [];
|
|
@@ -39,6 +40,13 @@ const lifecycleSource = `
|
|
|
39
40
|
globalThis.phase = async phase => {
|
|
40
41
|
assert.equal(await readFile(lockPath, 'utf8'), lockContents);
|
|
41
42
|
phases.push(phase);
|
|
43
|
+
assert.equal(app.meta.completedReadiness.snapshot().application, 'starting');
|
|
44
|
+
if (phase === 'Started') {
|
|
45
|
+
assert.equal(app.meta.appReady, true);
|
|
46
|
+
assert.equal(app.meta.appStarted, true);
|
|
47
|
+
await Promise.resolve();
|
|
48
|
+
assert.equal(app.meta.completedReadiness.snapshot().application, 'starting');
|
|
49
|
+
}
|
|
42
50
|
if (phase === failingPhase) throw failure;
|
|
43
51
|
};
|
|
44
52
|
const config = {
|
|
@@ -62,6 +70,7 @@ const lifecycleSource = `
|
|
|
62
70
|
events.push({ event, error, phases: [...phases] });
|
|
63
71
|
},
|
|
64
72
|
};
|
|
73
|
+
app.meta.completedReadiness = createCompletedReadiness(app);
|
|
65
74
|
const start = new Start(app);
|
|
66
75
|
const order = ['Config', 'Freshness', 'Load', 'Start', 'Ready', 'Started'];
|
|
67
76
|
if (failingPhase) {
|
|
@@ -76,6 +85,7 @@ const lifecycleSource = `
|
|
|
76
85
|
assert.deepEqual(phases, order);
|
|
77
86
|
assert.equal(app.meta.appStartError, undefined);
|
|
78
87
|
}
|
|
88
|
+
assert.equal(app.meta.completedReadiness.snapshot().application, failingPhase ? 'failed' : 'ready');
|
|
79
89
|
const reachesStarted = !failingPhase || failingPhase === 'Started';
|
|
80
90
|
assert.equal(app.meta.appStarted, reachesStarted ? true : undefined);
|
|
81
91
|
const reachesReady = reachesStarted || failingPhase === 'Ready';
|
|
@@ -2,6 +2,10 @@ import type { VonaConfig, VonaConfigOptional } from '../../types/index.ts';
|
|
|
2
2
|
import type { VonaApplication } from '../core/application.ts';
|
|
3
3
|
|
|
4
4
|
import { EnumAppEvent } from '../../types/index.ts';
|
|
5
|
+
import {
|
|
6
|
+
beginCompletedApplication,
|
|
7
|
+
finishCompletedApplication,
|
|
8
|
+
} from '../core/completedReadiness.ts';
|
|
5
9
|
import { assertIsolatedTestConfig } from '../core/isolationGuard.ts';
|
|
6
10
|
import { registerTestClaimResourceInventory } from '../core/testClaim.ts';
|
|
7
11
|
import { assertFreshTestResources } from '../core/testFreshness.ts';
|
|
@@ -17,6 +21,8 @@ export class Start {
|
|
|
17
21
|
|
|
18
22
|
async start() {
|
|
19
23
|
const app = this.app;
|
|
24
|
+
const owner = app.meta.completedReadiness;
|
|
25
|
+
const attempt = beginCompletedApplication(owner);
|
|
20
26
|
try {
|
|
21
27
|
await this._start_appConfig();
|
|
22
28
|
await assertFreshTestResources(app);
|
|
@@ -24,7 +30,9 @@ export class Start {
|
|
|
24
30
|
await this._start_appStart();
|
|
25
31
|
await this._start_appReady();
|
|
26
32
|
await this._start_appStarted();
|
|
33
|
+
finishCompletedApplication(owner, attempt, true);
|
|
27
34
|
} catch (err) {
|
|
35
|
+
finishCompletedApplication(owner, attempt, false);
|
|
28
36
|
// record
|
|
29
37
|
app.meta.appStartError = err as Error;
|
|
30
38
|
// event: appReadyError
|