@o3co/create-auth-provider 0.16.0 → 0.17.0-rc.2
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/README.ja.md +66 -21
- package/README.md +76 -31
- package/dist/index.d.mts +33 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +118 -59
- package/dist/internal/template-filter.d.mts +7 -10
- package/dist/internal/template-filter.d.mts.map +1 -1
- package/dist/internal/template-filter.mjs +7 -10
- package/package.json +6 -4
- package/templates/standalone/.dockerignore +15 -2
- package/templates/standalone/.env.example +59 -29
- package/templates/standalone/Dockerfile +6 -2
- package/templates/standalone/README.ja.md +324 -169
- package/templates/standalone/README.md +618 -208
- package/templates/standalone/config/application.conf +211 -352
- package/templates/standalone/config/clients.yaml +2 -0
- package/templates/standalone/config/clients.yaml.example +4 -0
- package/templates/standalone/config/development.conf +13 -4
- package/templates/standalone/config/mailpit.conf +8 -0
- package/templates/standalone/config/production.conf +5 -6
- package/templates/standalone/config/reference.conf +418 -0
- package/templates/standalone/docker-compose.mailpit.yml +36 -0
- package/templates/standalone/docker-compose.production.yml +63 -32
- package/templates/standalone/docker-compose.yml +13 -3
- package/templates/standalone/gitignore +1 -0
- package/templates/standalone/package.json +9 -6
- package/templates/standalone/src/__tests__/adapters.test.mts +419 -0
- package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +87 -0
- package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +287 -198
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +71 -35
- package/templates/standalone/src/__tests__/all-modules-composition.test.mts +148 -94
- package/templates/standalone/src/__tests__/audit-sink.test.mts +84 -81
- package/templates/standalone/src/__tests__/config-notices.test.mts +154 -0
- package/templates/standalone/src/__tests__/configPath.test.mts +13 -11
- package/templates/standalone/src/__tests__/core-federations.test.mts +249 -0
- package/templates/standalone/src/__tests__/core-sections.test.mts +190 -0
- package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +10 -15
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +970 -288
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +421 -86
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +87 -72
- package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +16 -8
- package/templates/standalone/src/__tests__/federation-types.test.mts +239 -0
- package/templates/standalone/src/__tests__/google-session.fixture.mts +97 -93
- package/templates/standalone/src/__tests__/host-routes.test.mts +4 -11
- package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +1 -1
- package/templates/standalone/src/__tests__/library-references.fixture.mts +188 -0
- package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +216 -0
- package/templates/standalone/src/__tests__/listen.test.mts +6 -13
- package/templates/standalone/src/__tests__/logger-projection.test.mts +1 -1
- package/templates/standalone/src/__tests__/mail-sender.test.mts +134 -0
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +326 -0
- package/templates/standalone/src/__tests__/metrics.test.mts +4 -9
- package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +122 -0
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +123 -0
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +825 -0
- package/templates/standalone/src/__tests__/own-modules.test.mts +1486 -0
- package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +10 -14
- package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +1 -5
- package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +116 -81
- package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +25 -31
- package/templates/standalone/src/__tests__/replica-safety.test.mts +265 -146
- package/templates/standalone/src/__tests__/router-error-answers.test.mts +4 -7
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +278 -103
- package/templates/standalone/src/__tests__/sections.test.mts +480 -0
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +215 -0
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +69 -61
- package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +130 -0
- package/templates/standalone/src/__tests__/session-requirements.test.mts +382 -0
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +270 -0
- package/templates/standalone/src/__tests__/shutdown.test.mts +163 -96
- package/templates/standalone/src/__tests__/smoke.test.mts +176 -258
- package/templates/standalone/src/__tests__/store-credential.test.mts +88 -52
- package/templates/standalone/src/__tests__/supertest-loopback.test.mts +182 -11
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +131 -102
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +604 -0
- package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +382 -0
- package/templates/standalone/src/adapters.mts +162 -0
- package/templates/standalone/src/app.mts +65 -79
- package/templates/standalone/src/bootRefusal.mts +117 -0
- package/templates/standalone/src/buildModules.mts +220 -309
- package/templates/standalone/src/configPath.mts +449 -29
- package/templates/standalone/src/listen.mts +12 -18
- package/templates/standalone/src/logger.mts +31 -60
- package/templates/standalone/src/metrics.mts +24 -47
- package/templates/standalone/src/mfaSwitch.mts +285 -0
- package/templates/standalone/src/modules.mts +423 -629
- package/templates/standalone/src/rootRenames.mts +96 -0
- package/templates/standalone/src/routes.mts +8 -14
- package/templates/standalone/src/sections.mts +367 -0
- package/templates/standalone/src/shutdown.mts +45 -123
- package/templates/standalone/tests/index.test.js +6 -5
- package/templates/standalone/tests/package-lock.json +1615 -2223
- package/templates/standalone/tests/package.json +2 -2
- package/templates/standalone/tests/vitest.config.mjs +26 -0
- package/templates/standalone/vitest.config.mts +8 -7
- package/templates/standalone/vitest.supertest-loopback.mts +97 -69
- package/templates/versions.json +18 -15
- package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +0 -135
- package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +0 -199
- package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +0 -138
|
@@ -15,19 +15,11 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* guarantees
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* waits for in-flight requests indefinitely, so one stuck request meant the
|
|
24
|
-
* process never exited on its own and the orchestrator's SIGKILL took it down
|
|
25
|
-
* mid-flight — the opposite of a graceful shutdown, and invisible until it
|
|
26
|
-
* happened. The cleanup-failure path also wrote to `console.error`, a bare
|
|
27
|
-
* line in a service whose every other line is NDJSON.
|
|
28
|
-
*
|
|
29
|
-
* So the behaviour lives here now, with a deadline and the app's own logger,
|
|
30
|
-
* and these tests are the contract the issue said was missing.
|
|
18
|
+
* The contract of the template's graceful shutdown (README, "Shutdown
|
|
19
|
+
* guarantees"): a force-close deadline on the drain, a bounded cleanup, and
|
|
20
|
+
* every stage logged through the app's own logger. `server.close()` alone
|
|
21
|
+
* waits for in-flight requests indefinitely, so one stuck request would keep
|
|
22
|
+
* the process up until the orchestrator's SIGKILL took it down mid-flight.
|
|
31
23
|
*/
|
|
32
24
|
|
|
33
25
|
import { createServer, type Server } from "node:http";
|
|
@@ -40,13 +32,7 @@ import {
|
|
|
40
32
|
} from "@o3co/auth-provider-core";
|
|
41
33
|
import { makeValidCoreConfig } from "@o3co/auth-provider-core/testing";
|
|
42
34
|
import { describe, expect, it, vi } from "vitest";
|
|
43
|
-
import {
|
|
44
|
-
cleanupAllowanceFor,
|
|
45
|
-
deferExit,
|
|
46
|
-
FEDERATION_GRANTS_CLEANUP_ALLOWANCE_MS,
|
|
47
|
-
FEDERATION_GRANTS_CLEANUP_MARGIN_MS,
|
|
48
|
-
installGracefulShutdown,
|
|
49
|
-
} from "../shutdown.mjs";
|
|
35
|
+
import { deferExit, installGracefulShutdown } from "../shutdown.mjs";
|
|
50
36
|
|
|
51
37
|
/** A `Server` double whose `close` callback fires only when we say so. */
|
|
52
38
|
function makeServer() {
|
|
@@ -74,8 +60,7 @@ function makeServer() {
|
|
|
74
60
|
|
|
75
61
|
/**
|
|
76
62
|
* A `Logger`-shaped spy. Typed rather than cast: `as never` would hide a real
|
|
77
|
-
* mismatch the day the port gains a method
|
|
78
|
-
* shutdown path logs through the app logger instead of `console`.
|
|
63
|
+
* mismatch the day the port gains a method.
|
|
79
64
|
*/
|
|
80
65
|
const makeLogger = () => {
|
|
81
66
|
const spy = {
|
|
@@ -134,6 +119,7 @@ function install(
|
|
|
134
119
|
cleanup?: () => void | Promise<void>;
|
|
135
120
|
drainTimeoutMs?: number;
|
|
136
121
|
cleanupTimeoutMs?: number;
|
|
122
|
+
cleanupAllowanceMs?: () => number | undefined;
|
|
137
123
|
logger?: ReturnType<typeof makeLogger>;
|
|
138
124
|
} = {},
|
|
139
125
|
) {
|
|
@@ -147,6 +133,9 @@ function install(
|
|
|
147
133
|
cleanup: opts.cleanup ?? (() => {}),
|
|
148
134
|
...(opts.drainTimeoutMs === undefined ? {} : { drainTimeoutMs: opts.drainTimeoutMs }),
|
|
149
135
|
...(opts.cleanupTimeoutMs === undefined ? {} : { cleanupTimeoutMs: opts.cleanupTimeoutMs }),
|
|
136
|
+
...(opts.cleanupAllowanceMs === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: { cleanupAllowanceMs: opts.cleanupAllowanceMs }),
|
|
150
139
|
exit,
|
|
151
140
|
onSignal: (name, handler) => signals.set(name, handler),
|
|
152
141
|
offSignal: (name) => signals.delete(name),
|
|
@@ -155,7 +144,7 @@ function install(
|
|
|
155
144
|
return { spies, logger, exit, signals, finishDraining, failClose };
|
|
156
145
|
}
|
|
157
146
|
|
|
158
|
-
describe("installGracefulShutdown
|
|
147
|
+
describe("installGracefulShutdown", () => {
|
|
159
148
|
it("listens for both SIGTERM and SIGINT", async () => {
|
|
160
149
|
const { signals } = install();
|
|
161
150
|
expect([...signals.keys()].sort()).toEqual(["SIGINT", "SIGTERM"]);
|
|
@@ -192,7 +181,6 @@ describe("installGracefulShutdown (#290)", () => {
|
|
|
192
181
|
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
193
182
|
});
|
|
194
183
|
|
|
195
|
-
// The gap the audit was pointing at.
|
|
196
184
|
it("forces the remaining connections closed when draining outruns the deadline", async () => {
|
|
197
185
|
vi.useFakeTimers();
|
|
198
186
|
try {
|
|
@@ -217,7 +205,10 @@ describe("installGracefulShutdown (#290)", () => {
|
|
|
217
205
|
await vi.advanceTimersByTimeAsync(5_000);
|
|
218
206
|
expect(exit).toHaveBeenCalledWith(1);
|
|
219
207
|
// Each stage is an event: object-first, a snake_case name.
|
|
220
|
-
expect(logger.info).toHaveBeenCalledWith(
|
|
208
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
209
|
+
{ drainTimeoutMs: 5_000, cleanupTimeoutMs: 5_000 },
|
|
210
|
+
"shutdown_draining",
|
|
211
|
+
);
|
|
221
212
|
expect(logger.error).toHaveBeenCalledWith(
|
|
222
213
|
{ drainTimeoutMs: 5_000 },
|
|
223
214
|
"shutdown_drain_deadline_exceeded",
|
|
@@ -385,11 +376,9 @@ describe("installGracefulShutdown (#290)", () => {
|
|
|
385
376
|
});
|
|
386
377
|
|
|
387
378
|
it("bounds cleanup so a hanging dispose cannot wedge the process", async () => {
|
|
388
|
-
// The
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
// The same defect was found in auth.proxy#81 and auth.policy-verifier#210,
|
|
392
|
-
// both of which took this file as their starting point.
|
|
379
|
+
// The drain deadline is already cleared when cleanup runs, so without a
|
|
380
|
+
// deadline of its own a dispose that never settles would keep `exit`
|
|
381
|
+
// from ever being reached.
|
|
393
382
|
vi.useFakeTimers();
|
|
394
383
|
try {
|
|
395
384
|
const { signals, finishDraining, exit, logger } = install({
|
|
@@ -437,9 +426,9 @@ describe("installGracefulShutdown (#290)", () => {
|
|
|
437
426
|
});
|
|
438
427
|
|
|
439
428
|
it("reports the cleanup outcome as the reason, not the drain that preceded it", async () => {
|
|
440
|
-
// `
|
|
441
|
-
//
|
|
442
|
-
//
|
|
429
|
+
// `reason` must agree with `exitCode` on the one line an operator alerts
|
|
430
|
+
// on. The drain outcome is carried under its own key, so neither fact is
|
|
431
|
+
// lost.
|
|
443
432
|
const { signals, finishDraining, logger, exit } = install({
|
|
444
433
|
cleanup: () => Promise.reject(new Error("teardown failed")),
|
|
445
434
|
});
|
|
@@ -473,81 +462,159 @@ describe("installGracefulShutdown (#290)", () => {
|
|
|
473
462
|
});
|
|
474
463
|
});
|
|
475
464
|
|
|
476
|
-
describe("
|
|
477
|
-
it("
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
465
|
+
describe("the cleanup allowance the handle reports", () => {
|
|
466
|
+
it("gives cleanup the allowance when it is longer than the drain", async () => {
|
|
467
|
+
vi.useFakeTimers();
|
|
468
|
+
try {
|
|
469
|
+
const { signals, finishDraining, exit } = install({
|
|
470
|
+
cleanup: () => new Promise<void>((resolve) => setTimeout(resolve, 30_000)),
|
|
471
|
+
drainTimeoutMs: 5_000,
|
|
472
|
+
cleanupAllowanceMs: () => 45_000,
|
|
473
|
+
});
|
|
474
|
+
signals.get("SIGTERM")?.();
|
|
475
|
+
finishDraining();
|
|
476
|
+
await vi.advanceTimersByTimeAsync(31_000);
|
|
477
|
+
expect(exit).toHaveBeenCalledExactlyOnceWith(0);
|
|
478
|
+
} finally {
|
|
479
|
+
vi.useRealTimers();
|
|
480
|
+
}
|
|
489
481
|
});
|
|
490
482
|
|
|
491
|
-
it("
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
).toEqual({ cleanupTimeoutMs: 60_000 + 3_000 + 5_000 + FEDERATION_GRANTS_CLEANUP_MARGIN_MS });
|
|
513
|
-
// Lowered budgets never go below the documented minimum, and a config
|
|
514
|
-
// without budgets (built by hand) gets the floor rather than a guess.
|
|
515
|
-
expect(
|
|
516
|
-
cleanupAllowanceFor({
|
|
517
|
-
federationGrants: { enabled: true, ...shipped, upstreamHardTimeoutMs: 1_000 },
|
|
518
|
-
}),
|
|
519
|
-
).toEqual({ cleanupTimeoutMs: FEDERATION_GRANTS_CLEANUP_ALLOWANCE_MS });
|
|
520
|
-
expect(
|
|
521
|
-
cleanupAllowanceFor({ federationGrants: { enabled: true, upstreamHardTimeoutMs: 90_000 } }),
|
|
522
|
-
).toEqual({ cleanupTimeoutMs: FEDERATION_GRANTS_CLEANUP_ALLOWANCE_MS });
|
|
483
|
+
it("gives up on cleanup at the allowance, and logs it as the budget", async () => {
|
|
484
|
+
vi.useFakeTimers();
|
|
485
|
+
try {
|
|
486
|
+
const { signals, finishDraining, exit, logger } = install({
|
|
487
|
+
cleanup: () => new Promise<void>(() => {}),
|
|
488
|
+
drainTimeoutMs: 5_000,
|
|
489
|
+
cleanupAllowanceMs: () => 45_000,
|
|
490
|
+
});
|
|
491
|
+
signals.get("SIGTERM")?.();
|
|
492
|
+
finishDraining();
|
|
493
|
+
await vi.advanceTimersByTimeAsync(44_999);
|
|
494
|
+
expect(exit).not.toHaveBeenCalled();
|
|
495
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
496
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
497
|
+
{ cleanupTimeoutMs: 45_000 },
|
|
498
|
+
"shutdown_cleanup_timed_out",
|
|
499
|
+
);
|
|
500
|
+
expect(exit).toHaveBeenCalledExactlyOnceWith(1);
|
|
501
|
+
} finally {
|
|
502
|
+
vi.useRealTimers();
|
|
503
|
+
}
|
|
523
504
|
});
|
|
524
505
|
|
|
525
|
-
it("
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
506
|
+
it("keeps the longer budget when the allowance is shorter", async () => {
|
|
507
|
+
vi.useFakeTimers();
|
|
508
|
+
try {
|
|
509
|
+
const { signals, finishDraining, exit, logger } = install({
|
|
510
|
+
cleanup: () => new Promise<void>(() => {}),
|
|
511
|
+
drainTimeoutMs: 5_000,
|
|
512
|
+
cleanupAllowanceMs: () => 2_000,
|
|
513
|
+
});
|
|
514
|
+
signals.get("SIGTERM")?.();
|
|
515
|
+
finishDraining();
|
|
516
|
+
await vi.advanceTimersByTimeAsync(4_999);
|
|
517
|
+
expect(exit).not.toHaveBeenCalled();
|
|
518
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
519
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
520
|
+
{ cleanupTimeoutMs: 5_000 },
|
|
521
|
+
"shutdown_cleanup_timed_out",
|
|
522
|
+
);
|
|
523
|
+
} finally {
|
|
524
|
+
vi.useRealTimers();
|
|
525
|
+
}
|
|
537
526
|
});
|
|
538
527
|
|
|
539
|
-
it("
|
|
528
|
+
it("leaves cleanup its own budget when the handle reports no allowance", async () => {
|
|
540
529
|
vi.useFakeTimers();
|
|
541
530
|
try {
|
|
542
|
-
const { signals, finishDraining,
|
|
543
|
-
cleanup: () => new Promise<void>((
|
|
531
|
+
const { signals, finishDraining, logger } = install({
|
|
532
|
+
cleanup: () => new Promise<void>(() => {}),
|
|
544
533
|
drainTimeoutMs: 5_000,
|
|
545
|
-
|
|
534
|
+
cleanupAllowanceMs: () => undefined,
|
|
546
535
|
});
|
|
547
536
|
signals.get("SIGTERM")?.();
|
|
548
537
|
finishDraining();
|
|
549
|
-
await vi.advanceTimersByTimeAsync(
|
|
550
|
-
expect(
|
|
538
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
539
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
540
|
+
{ cleanupTimeoutMs: 5_000 },
|
|
541
|
+
"shutdown_cleanup_timed_out",
|
|
542
|
+
);
|
|
543
|
+
} finally {
|
|
544
|
+
vi.useRealTimers();
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("reads the allowance when the signal arrives, so a tail registered after install counts", async () => {
|
|
549
|
+
vi.useFakeTimers();
|
|
550
|
+
try {
|
|
551
|
+
let allowance: number | undefined;
|
|
552
|
+
const { signals, finishDraining, exit, logger } = install({
|
|
553
|
+
cleanup: () => new Promise<void>(() => {}),
|
|
554
|
+
drainTimeoutMs: 5_000,
|
|
555
|
+
cleanupAllowanceMs: () => allowance,
|
|
556
|
+
});
|
|
557
|
+
allowance = 60_000;
|
|
558
|
+
signals.get("SIGTERM")?.();
|
|
559
|
+
expect(logger.info).toHaveBeenCalledWith(
|
|
560
|
+
{ drainTimeoutMs: 5_000, cleanupTimeoutMs: 60_000 },
|
|
561
|
+
"shutdown_draining",
|
|
562
|
+
);
|
|
563
|
+
finishDraining();
|
|
564
|
+
await vi.advanceTimersByTimeAsync(59_999);
|
|
565
|
+
expect(exit).not.toHaveBeenCalled();
|
|
566
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
567
|
+
expect(exit).toHaveBeenCalledExactlyOnceWith(1);
|
|
568
|
+
} finally {
|
|
569
|
+
vi.useRealTimers();
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it.each([Number.NaN, Number.POSITIVE_INFINITY, -1, 0, 1.5, 2_147_483_648])(
|
|
574
|
+
"treats an allowance of %s as absent: cleanup keeps its own budget",
|
|
575
|
+
async (allowance) => {
|
|
576
|
+
// Handed to a timer, NaN or a delay past 2147483647 fires after about a
|
|
577
|
+
// millisecond, which would turn the floor into no budget at all.
|
|
578
|
+
vi.useFakeTimers();
|
|
579
|
+
try {
|
|
580
|
+
const { signals, finishDraining, exit, logger } = install({
|
|
581
|
+
cleanup: () => new Promise<void>(() => {}),
|
|
582
|
+
drainTimeoutMs: 5_000,
|
|
583
|
+
cleanupAllowanceMs: () => allowance,
|
|
584
|
+
});
|
|
585
|
+
signals.get("SIGTERM")?.();
|
|
586
|
+
finishDraining();
|
|
587
|
+
await vi.advanceTimersByTimeAsync(4_999);
|
|
588
|
+
expect(exit).not.toHaveBeenCalled();
|
|
589
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
590
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
591
|
+
{ cleanupTimeoutMs: 5_000 },
|
|
592
|
+
"shutdown_cleanup_timed_out",
|
|
593
|
+
);
|
|
594
|
+
} finally {
|
|
595
|
+
vi.useRealTimers();
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
it("treats an allowance that cannot be read as absent, and still shuts down", async () => {
|
|
601
|
+
vi.useFakeTimers();
|
|
602
|
+
try {
|
|
603
|
+
const { signals, finishDraining, exit, logger } = install({
|
|
604
|
+
cleanup: () => new Promise<void>(() => {}),
|
|
605
|
+
drainTimeoutMs: 5_000,
|
|
606
|
+
cleanupAllowanceMs: () => {
|
|
607
|
+
throw new Error("allowance unavailable");
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
signals.get("SIGTERM")?.();
|
|
611
|
+
finishDraining();
|
|
612
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
613
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
614
|
+
{ cleanupTimeoutMs: 5_000 },
|
|
615
|
+
"shutdown_cleanup_timed_out",
|
|
616
|
+
);
|
|
617
|
+
expect(exit).toHaveBeenCalledExactlyOnceWith(1);
|
|
551
618
|
} finally {
|
|
552
619
|
vi.useRealTimers();
|
|
553
620
|
}
|