@teamscala/service-boot 0.23.539 → 0.23.541
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/package.json +5 -1
- package/src/apply-logger-config.test.ts +13 -8
- package/src/attach-portal-members.test.ts +4 -0
- package/src/boot-shell.test.ts +57 -0
- package/src/boot-shell.ts +20 -0
- package/src/build-client-assets/sourcemaps-upload.test.ts +51 -6
- package/src/error-page-logo.test.ts +35 -15
- package/src/mount-orpc-router.test.ts +8 -0
- package/src/orpc-contract-rows.test.ts +10 -9
- package/src/page-server-loader-graph.test.ts +10 -6
- package/src/read-html-template.test.ts +9 -1
- package/src/readiness-gate.ts +4 -0
- package/src/register-registry-page-route/apply-page-branding.test.ts +13 -1
- package/src/register-registry-page-route/apply-page-branding.ts +1 -1
- package/src/register-registry-page-route/live-loader-config.test.ts +13 -4
- package/src/register-registry-page-route/meta-title.test.ts +8 -8
- package/src/register-registry-page-route/page-data-shared.test.ts +12 -5
- package/src/register-registry-page-route/resolve-entity-title.test.ts +9 -1
- package/src/register-registry-page-route/resolve-page-data.test.ts +16 -1
- package/src/register-registry-page-route/resolve-request-graph.test.ts +12 -26
- package/src/resolve-brand-favicon.test.ts +13 -11
- package/src/resolve-theme-mode.test.ts +52 -27
- package/src/resolvers/fetch-org-brand.test.ts +23 -12
- package/src/resolvers/website-by-host-map.test.ts +32 -16
- package/src/resource-not-found.test.ts +19 -1
- package/src/run-boot-hook-list.test.ts +10 -8
- package/src/run-boot-hooks.test.ts +11 -25
- package/src/scala-tools-currency-watchdog.test.ts +14 -6
- package/src/scala-tools-currency-watchdog.ts +5 -1
- package/src/theme-fonts-link.test.ts +2 -0
- package/src/two-phase-full-app.test.ts +69 -2
- package/src/two-phase-full-app.ts +29 -6
- package/src/two-phase-health-gate.test.ts +24 -0
- package/src/two-phase-minimal-app.test.ts +2 -2
- package/src/two-phase-readiness.test.ts +84 -34
- package/src/two-phase-readiness.ts +33 -7
- package/src/types.test.ts +3 -4
- package/src/ui-service.test.ts +2 -2
- package/src/ui-service.ts +55 -23
- package/src/ws-boot-self-test.test.ts +15 -8
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @status generated — ts-api-smoke template (scala-test-suites
|
|
3
|
-
* templates/ts_api_smoke.pkl @1.
|
|
3
|
+
* templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
|
|
4
4
|
* ./meta-title declares 3 runtime export(s) and 4 declared behaviour vector(s);
|
|
5
5
|
* this suite asserts the import resolves, the runtime surface matches
|
|
6
6
|
* exactly, and every export carries its declared kind. Regenerate from
|
|
7
|
-
* inputs/server-service-boot
|
|
7
|
+
* inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
|
|
8
8
|
* overwritten.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -32,19 +32,19 @@ describe("meta-title exports-map smoke", () => {
|
|
|
32
32
|
expect(typeof mod["resolveEntityTitle"]).toBe("function");
|
|
33
33
|
});
|
|
34
34
|
it("an absent meta context resolves no meta at all", async () => {
|
|
35
|
-
const got = buildMetaFromContext(undefined, { page: { name: "Quiz" } })
|
|
35
|
+
const got = buildMetaFromContext(undefined, { page: { name: "Quiz" } })
|
|
36
36
|
expect(got).toEqual({});
|
|
37
37
|
});
|
|
38
38
|
it("the page's own meta_title wins, from loaderData first", async () => {
|
|
39
|
-
const got = resolvePageMetaTitle({ loaderData: { page: { meta_title: "Sign in" } }, page: { meta_title: "Landing" } })
|
|
40
|
-
expect(got).
|
|
39
|
+
const got = resolvePageMetaTitle({ loaderData: { page: { meta_title: "Sign in" } }, page: { meta_title: "Landing" } })
|
|
40
|
+
expect(got).toEqual("Sign in");
|
|
41
41
|
});
|
|
42
42
|
it("with no loaderData page, the resolved page's meta_title answers", async () => {
|
|
43
|
-
const got = resolvePageMetaTitle({ loaderData: {}, page: { meta_title: "Landing" } })
|
|
44
|
-
expect(got).
|
|
43
|
+
const got = resolvePageMetaTitle({ loaderData: {}, page: { meta_title: "Landing" } })
|
|
44
|
+
expect(got).toEqual("Landing");
|
|
45
45
|
});
|
|
46
46
|
it("neither page carries a title, so no title resolves", async () => {
|
|
47
|
-
const got = resolvePageMetaTitle({ loaderData: {} })
|
|
47
|
+
const got = resolvePageMetaTitle({ loaderData: {} })
|
|
48
48
|
expect(got).toBeUndefined();
|
|
49
49
|
});
|
|
50
50
|
});
|
|
@@ -6,16 +6,23 @@
|
|
|
6
6
|
// carries them verbatim, this file is the emission, and hand edits here are
|
|
7
7
|
// overwritten on the next run. The rationale each assertion carries moved
|
|
8
8
|
// with it into the band.
|
|
9
|
+
import * as __scalaReal_teamscala_logger_app_loggers from "@teamscala/logger/app-loggers";
|
|
9
10
|
|
|
10
11
|
import { describe, expect, mock, test } from "bun:test";
|
|
11
12
|
|
|
12
13
|
const warns: string[] = [];
|
|
14
|
+
// Full five-method surface: mock.module installs PROCESS-WIDE, so a partial
|
|
15
|
+
// double breaks every suite that imports the logger later (measured 2026-09-30).
|
|
16
|
+
const loggerDouble = {
|
|
17
|
+
debug: () => {},
|
|
18
|
+
info: () => {},
|
|
19
|
+
warn: (message: string) => warns.push(message),
|
|
20
|
+
error: () => {},
|
|
21
|
+
child: () => loggerDouble,
|
|
22
|
+
};
|
|
13
23
|
mock.module("@teamscala/logger/app-loggers", () => ({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
info: () => {},
|
|
17
|
-
error: () => {},
|
|
18
|
-
}),
|
|
24
|
+
...__scalaReal_teamscala_logger_app_loggers,
|
|
25
|
+
getAppLogger: () => loggerDouble,
|
|
19
26
|
}));
|
|
20
27
|
|
|
21
28
|
import {
|
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
// carries them verbatim, this file is the emission, and hand edits here are
|
|
7
7
|
// overwritten on the next run. The rationale each assertion carries moved
|
|
8
8
|
// with it into the band.
|
|
9
|
+
import { afterAll as __scalaRestoreGlobals } from "bun:test";
|
|
10
|
+
import { GlobalRegistrator } from "@happy-dom/global-registrator";
|
|
11
|
+
const __scalaDomWasMissing = !globalThis.document;
|
|
12
|
+
if (__scalaDomWasMissing) GlobalRegistrator.register();
|
|
13
|
+
__scalaRestoreGlobals(async () => {
|
|
14
|
+
if (__scalaDomWasMissing) await GlobalRegistrator.unregister();
|
|
15
|
+
});
|
|
16
|
+
|
|
9
17
|
|
|
10
18
|
import { describe, expect, test } from "bun:test";
|
|
11
|
-
|
|
19
|
+
const { resolveEntityTitle } = await import("./meta-title.ts");
|
|
12
20
|
|
|
13
21
|
describe("resolveEntityTitle", () => {
|
|
14
22
|
test("a report is named by its own document name, not the diagnostic", () => {
|
|
@@ -6,17 +6,32 @@
|
|
|
6
6
|
// carries them verbatim, this file is the emission, and hand edits here are
|
|
7
7
|
// overwritten on the next run. The rationale each assertion carries moved
|
|
8
8
|
// with it into the band.
|
|
9
|
+
import * as __scalaReal_teamscala_service_boot_page_server_orpc_server_client from "@teamscala/service-boot/page-server-orpc/server-client";
|
|
10
|
+
import * as __scalaReal_teamscala_db_registry_load_config from "@teamscala/db/registry/load-config";
|
|
11
|
+
import * as __scalaReal_teamscala_logger_app_loggers from "@teamscala/logger/app-loggers";
|
|
9
12
|
|
|
10
13
|
import { describe, expect, mock, test } from "bun:test";
|
|
11
14
|
|
|
12
15
|
const warn = mock((_message: string) => {});
|
|
16
|
+
// Full five-method surface: mock.module installs PROCESS-WIDE, so a partial
|
|
17
|
+
// double breaks every suite that imports the logger later (measured 2026-09-30).
|
|
18
|
+
const loggerDouble = {
|
|
19
|
+
debug: () => {},
|
|
20
|
+
info: () => {},
|
|
21
|
+
warn,
|
|
22
|
+
error: () => {},
|
|
23
|
+
child: () => loggerDouble,
|
|
24
|
+
};
|
|
13
25
|
mock.module("@teamscala/logger/app-loggers", () => ({
|
|
14
|
-
|
|
26
|
+
...__scalaReal_teamscala_logger_app_loggers,
|
|
27
|
+
getAppLogger: () => loggerDouble,
|
|
15
28
|
}));
|
|
16
29
|
mock.module("@teamscala/db/registry/load-config", () => ({
|
|
30
|
+
...__scalaReal_teamscala_db_registry_load_config,
|
|
17
31
|
loadRegistryConfig: async () => ({}),
|
|
18
32
|
}));
|
|
19
33
|
mock.module("@teamscala/service-boot/page-server-orpc/server-client", () => ({
|
|
34
|
+
...__scalaReal_teamscala_service_boot_page_server_orpc_server_client,
|
|
20
35
|
getServerClient: () => ({}),
|
|
21
36
|
}));
|
|
22
37
|
|
|
@@ -6,18 +6,25 @@
|
|
|
6
6
|
// carries them verbatim, this file is the emission, and hand edits here are
|
|
7
7
|
// overwritten on the next run. The rationale each assertion carries moved
|
|
8
8
|
// with it into the band.
|
|
9
|
+
import * as __scalaReal_teamscala_logger_app_loggers from "@teamscala/logger/app-loggers";
|
|
9
10
|
|
|
10
11
|
import { describe, expect, mock, test } from "bun:test";
|
|
11
12
|
|
|
12
13
|
let loaderGraph: Record<string, unknown> = {};
|
|
13
14
|
const resolveCalls: Array<{ params: Record<string, string> }> = [];
|
|
14
15
|
|
|
16
|
+
// Full five-method surface: mock.module installs PROCESS-WIDE, so a partial
|
|
17
|
+
// double breaks every suite that imports the logger later (measured 2026-09-30).
|
|
18
|
+
const loggerDouble = {
|
|
19
|
+
debug: () => {},
|
|
20
|
+
info: () => {},
|
|
21
|
+
warn: () => {},
|
|
22
|
+
error: () => {},
|
|
23
|
+
child: () => loggerDouble,
|
|
24
|
+
};
|
|
15
25
|
mock.module("@teamscala/logger/app-loggers", () => ({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
info: () => {},
|
|
19
|
-
error: () => {},
|
|
20
|
-
}),
|
|
26
|
+
...__scalaReal_teamscala_logger_app_loggers,
|
|
27
|
+
getAppLogger: () => loggerDouble,
|
|
21
28
|
}));
|
|
22
29
|
|
|
23
30
|
mock.module("../page-server-loader-graph.ts", () => ({
|
|
@@ -60,12 +67,6 @@ mock.module("../content-page-floor.ts", () => ({
|
|
|
60
67
|
},
|
|
61
68
|
}));
|
|
62
69
|
|
|
63
|
-
mock.module("./graph-cache.ts", () => ({
|
|
64
|
-
staticShellGraphCache: { getWithMeta: () => undefined, set: () => {}, refreshInBackground: () => {} },
|
|
65
|
-
paramRouteGraphCache: { getWithMeta: () => undefined, set: () => {}, refreshInBackground: () => {} },
|
|
66
|
-
readCachedGraph: () => undefined,
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
70
|
import { resolveRequestGraph, type ResolveRequestGraphArgs } from "./resolve-request-graph.ts";
|
|
70
71
|
import type { PageRouteConfig } from "../register-registry-page-route.ts";
|
|
71
72
|
|
|
@@ -88,12 +89,9 @@ describe("resolveRequestGraph", () => {
|
|
|
88
89
|
test("a bare root with a resource_type throws the 200 holding-page signal", async () => {
|
|
89
90
|
const err = (await resolveRequestGraph({
|
|
90
91
|
pattern: "/",
|
|
91
|
-
routeSlug: "proposal",
|
|
92
92
|
routeConfig: route({ pattern: "/", resource_type: "proposal" }),
|
|
93
93
|
loaderConfig: loader([{ key: "proposal" }]),
|
|
94
94
|
params: { host: "proposals.scala.business", path: "/" },
|
|
95
|
-
isStaticShell: false,
|
|
96
|
-
serviceSlug: "scala-proposals",
|
|
97
95
|
}).catch((e) => e)) as Error & { httpStatus: number; resourceType: string };
|
|
98
96
|
expect(err).toBeInstanceOf(Error);
|
|
99
97
|
expect((err as { httpStatus?: number }).httpStatus).toBe(200);
|
|
@@ -104,12 +102,9 @@ describe("resolveRequestGraph", () => {
|
|
|
104
102
|
loaderGraph = { proposal: null };
|
|
105
103
|
const err = (await resolveRequestGraph({
|
|
106
104
|
pattern: "/:id",
|
|
107
|
-
routeSlug: "proposal",
|
|
108
105
|
routeConfig: route({ resource_type: "proposal" }),
|
|
109
106
|
loaderConfig: loader([{ key: "proposal" }]),
|
|
110
107
|
params: { host: "proposals.scala.business", path: "/missing" },
|
|
111
|
-
isStaticShell: false,
|
|
112
|
-
serviceSlug: "scala-proposals",
|
|
113
108
|
}).catch((e) => e)) as Error & { httpStatus: number; resourceType: string };
|
|
114
109
|
expect((err as { httpStatus?: number }).httpStatus).toBe(404);
|
|
115
110
|
expect((err as { resourceType?: string }).resourceType).toBe("proposal");
|
|
@@ -119,12 +114,9 @@ describe("resolveRequestGraph", () => {
|
|
|
119
114
|
loaderGraph = { page: null };
|
|
120
115
|
const err = (await resolveRequestGraph({
|
|
121
116
|
pattern: "/:id",
|
|
122
|
-
routeSlug: "public-page",
|
|
123
117
|
routeConfig: route({ resource_type: undefined }),
|
|
124
118
|
loaderConfig: loader([{ key: "page" }]),
|
|
125
119
|
params: { host: "public.scala.business", path: "/deleted" },
|
|
126
|
-
isStaticShell: false,
|
|
127
|
-
serviceSlug: "scala-public",
|
|
128
120
|
}).catch((e) => e)) as Error & { httpStatus: number; empty: boolean };
|
|
129
121
|
expect((err as { httpStatus?: number }).httpStatus).toBe(404);
|
|
130
122
|
expect((err as { empty?: boolean }).empty).toBe(false);
|
|
@@ -134,12 +126,9 @@ describe("resolveRequestGraph", () => {
|
|
|
134
126
|
loaderGraph = { page: { content: [] } };
|
|
135
127
|
const err = (await resolveRequestGraph({
|
|
136
128
|
pattern: "/:id",
|
|
137
|
-
routeSlug: "public-page",
|
|
138
129
|
routeConfig: route({ resource_type: undefined }),
|
|
139
130
|
loaderConfig: loader([{ key: "page" }]),
|
|
140
131
|
params: { host: "public.scala.business", path: "/empty" },
|
|
141
|
-
isStaticShell: false,
|
|
142
|
-
serviceSlug: "scala-public",
|
|
143
132
|
}).catch((e) => e)) as Error & { httpStatus: number; empty: boolean };
|
|
144
133
|
expect((err as { httpStatus?: number }).httpStatus).toBe(200);
|
|
145
134
|
expect((err as { empty?: boolean }).empty).toBe(true);
|
|
@@ -149,12 +138,9 @@ describe("resolveRequestGraph", () => {
|
|
|
149
138
|
loaderGraph = { proposal: { id: "p1", name: "Q3" } };
|
|
150
139
|
const resolved = await resolveRequestGraph({
|
|
151
140
|
pattern: "/:id",
|
|
152
|
-
routeSlug: "proposal",
|
|
153
141
|
routeConfig: route({ resource_type: "proposal" }),
|
|
154
142
|
loaderConfig: loader([{ key: "proposal" }]),
|
|
155
143
|
params: { host: "proposals.scala.business", path: "/p1" },
|
|
156
|
-
isStaticShell: false,
|
|
157
|
-
serviceSlug: "scala-proposals",
|
|
158
144
|
});
|
|
159
145
|
expect((resolved.proposal as { id: string }).id).toBe("p1");
|
|
160
146
|
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @status generated — ts-api-smoke template (scala-test-suites
|
|
3
|
-
* templates/ts_api_smoke.pkl @1.
|
|
3
|
+
* templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
|
|
4
4
|
* ./resolve-brand-favicon declares 2 runtime export(s) and 5 declared behaviour vector(s);
|
|
5
5
|
* this suite asserts the import resolves, the runtime surface matches
|
|
6
6
|
* exactly, and every export carries its declared kind. Regenerate from
|
|
7
|
-
* inputs/server-service-boot
|
|
7
|
+
* inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
|
|
8
8
|
* overwritten.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { describe, expect, it } from "bun:test";
|
|
12
|
-
|
|
12
|
+
const { mock } = await import("bun:test");
|
|
13
13
|
const __realAssetProxy = await import("./asset-proxy");
|
|
14
14
|
let __probeCalls = 0;
|
|
15
15
|
let __probeBehaviour: "ok" | "notok" | "throw" = "ok";
|
|
@@ -34,30 +34,32 @@ describe("resolve-brand-favicon exports-map smoke", () => {
|
|
|
34
34
|
});
|
|
35
35
|
it("no brand org resolves null without probing the asset store", async () => {
|
|
36
36
|
__probeCalls = 0;
|
|
37
|
-
const got = await resolveBrandFaviconPath(undefined)
|
|
37
|
+
const got = await resolveBrandFaviconPath(undefined)
|
|
38
38
|
expect(got).toBeNull();
|
|
39
39
|
expect(__probeCalls).toBe(0);
|
|
40
40
|
});
|
|
41
|
-
it("a reachable favicon resolves the encoded convention path and
|
|
41
|
+
it("a reachable favicon resolves the encoded convention path and probes the asset store once", async () => {
|
|
42
42
|
__probeBehaviour = "ok"; __probeCalls = 0;
|
|
43
|
-
const got = await resolveBrandFaviconPath("brand-org-probe-1")
|
|
43
|
+
const got = await resolveBrandFaviconPath("brand-org-probe-1")
|
|
44
44
|
expect(got).toBe("/api/assets/brand-org-probe-1%2Flogos%2Ffavicon.png");
|
|
45
45
|
expect(__probeCalls).toBe(1);
|
|
46
46
|
});
|
|
47
|
-
it("the second resolution for the same org serves from cache", async () => {
|
|
47
|
+
it("the second resolution for the same org serves from cache without probing again", async () => {
|
|
48
|
+
__probeBehaviour = "ok"; __probeCalls = 0;
|
|
49
|
+
await resolveBrandFaviconPath("brand-org-probe-4");
|
|
48
50
|
__probeCalls = 0;
|
|
49
|
-
const got = await resolveBrandFaviconPath("brand-org-probe-
|
|
50
|
-
expect(got).toBe("/api/assets/brand-org-probe-
|
|
51
|
+
const got = await resolveBrandFaviconPath("brand-org-probe-4")
|
|
52
|
+
expect(got).toBe("/api/assets/brand-org-probe-4%2Flogos%2Ffavicon.png");
|
|
51
53
|
expect(__probeCalls).toBe(0);
|
|
52
54
|
});
|
|
53
55
|
it("a missing favicon at the convention key resolves null", async () => {
|
|
54
56
|
__probeBehaviour = "notok"; __probeCalls = 0;
|
|
55
|
-
const got = await resolveBrandFaviconPath("brand-org-probe-2")
|
|
57
|
+
const got = await resolveBrandFaviconPath("brand-org-probe-2")
|
|
56
58
|
expect(got).toBeNull();
|
|
57
59
|
});
|
|
58
60
|
it("an asset-store failure degrades to the generic fallback (null), never a throw", async () => {
|
|
59
61
|
__probeBehaviour = "throw"; __probeCalls = 0;
|
|
60
|
-
const got = await resolveBrandFaviconPath("brand-org-probe-3")
|
|
62
|
+
const got = await resolveBrandFaviconPath("brand-org-probe-3")
|
|
61
63
|
expect(got).toBeNull();
|
|
62
64
|
});
|
|
63
65
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @status generated — ts-api-smoke template (scala-test-suites
|
|
3
|
-
* templates/ts_api_smoke.pkl @1.
|
|
4
|
-
* ./resolve-theme-mode declares 1 runtime export(s) + 1 type(s) and
|
|
3
|
+
* templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
|
|
4
|
+
* ./resolve-theme-mode declares 1 runtime export(s) + 1 type(s) and 10 declared behaviour vector(s);
|
|
5
5
|
* this suite asserts the import resolves, the runtime surface matches
|
|
6
6
|
* exactly, and every export carries its declared kind. Regenerate from
|
|
7
|
-
* inputs/server-service-boot
|
|
7
|
+
* inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
|
|
8
8
|
* overwritten.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -23,29 +23,54 @@ describe("resolve-theme-mode exports-map smoke", () => {
|
|
|
23
23
|
it("resolveThemeMode is a function", () => {
|
|
24
24
|
expect(typeof mod["resolveThemeMode"]).toBe("function");
|
|
25
25
|
});
|
|
26
|
-
it("the ?theme= URL override wins and marks the render embedded", async () => {
|
|
27
|
-
const got = resolveThemeMode({ url: "https://x.local/p?theme=dark" })
|
|
28
|
-
|
|
29
|
-
expect(
|
|
30
|
-
});
|
|
31
|
-
it("
|
|
32
|
-
const got = resolveThemeMode({
|
|
33
|
-
|
|
34
|
-
expect(
|
|
35
|
-
});
|
|
36
|
-
it("
|
|
37
|
-
const got = resolveThemeMode({ override: "
|
|
38
|
-
|
|
39
|
-
expect(
|
|
40
|
-
});
|
|
41
|
-
it("
|
|
42
|
-
const got = resolveThemeMode({
|
|
43
|
-
|
|
44
|
-
expect(
|
|
45
|
-
});
|
|
46
|
-
it("
|
|
47
|
-
const got = resolveThemeMode({})
|
|
48
|
-
|
|
49
|
-
expect(
|
|
26
|
+
it("the ?theme= URL override wins and marks the render embedded [toBe]", async () => {
|
|
27
|
+
const got = resolveThemeMode({ url: "https://x.local/p?theme=dark" })
|
|
28
|
+
const got2 = (got.theme);
|
|
29
|
+
expect(got2).toEqual("dark");
|
|
30
|
+
});
|
|
31
|
+
it("the ?theme= URL override wins and marks the render embedded [toBe]", async () => {
|
|
32
|
+
const got = resolveThemeMode({ url: "https://x.local/p?theme=dark" })
|
|
33
|
+
const got2 = (got.embedded);
|
|
34
|
+
expect(got2).toEqual(true);
|
|
35
|
+
});
|
|
36
|
+
it("an explicit override behaves exactly like the URL form [toBe]", async () => {
|
|
37
|
+
const got = resolveThemeMode({ override: "dark" })
|
|
38
|
+
const got2 = (got.theme);
|
|
39
|
+
expect(got2).toEqual("dark");
|
|
40
|
+
});
|
|
41
|
+
it("an explicit override behaves exactly like the URL form [toBe]", async () => {
|
|
42
|
+
const got = resolveThemeMode({ override: "dark" })
|
|
43
|
+
const got2 = (got.embedded);
|
|
44
|
+
expect(got2).toEqual(true);
|
|
45
|
+
});
|
|
46
|
+
it("a non-light/dark override is not an override [toBe]", async () => {
|
|
47
|
+
const got = resolveThemeMode({ override: "purple", candidates: ["dark"] })
|
|
48
|
+
const got2 = (got.theme);
|
|
49
|
+
expect(got2).toEqual("dark");
|
|
50
|
+
});
|
|
51
|
+
it("a non-light/dark override is not an override [toBe]", async () => {
|
|
52
|
+
const got = resolveThemeMode({ override: "purple", candidates: ["dark"] })
|
|
53
|
+
const got2 = (got.embedded);
|
|
54
|
+
expect(got2).toEqual(false);
|
|
55
|
+
});
|
|
56
|
+
it("candidates are consulted in priority order [toBe]", async () => {
|
|
57
|
+
const got = resolveThemeMode({ candidates: [null, "light", "dark"] })
|
|
58
|
+
const got2 = (got.theme);
|
|
59
|
+
expect(got2).toEqual("light");
|
|
60
|
+
});
|
|
61
|
+
it("candidates are consulted in priority order [toBe]", async () => {
|
|
62
|
+
const got = resolveThemeMode({ candidates: [null, "light", "dark"] })
|
|
63
|
+
const got2 = (got.embedded);
|
|
64
|
+
expect(got2).toEqual(false);
|
|
65
|
+
});
|
|
66
|
+
it("nothing resolvable lands on light, never a crash [toBe]", async () => {
|
|
67
|
+
const got = resolveThemeMode({})
|
|
68
|
+
const got2 = (got.theme);
|
|
69
|
+
expect(got2).toEqual("light");
|
|
70
|
+
});
|
|
71
|
+
it("nothing resolvable lands on light, never a crash [toBe]", async () => {
|
|
72
|
+
const got = resolveThemeMode({})
|
|
73
|
+
const got2 = (got.embedded);
|
|
74
|
+
expect(got2).toEqual(false);
|
|
50
75
|
});
|
|
51
76
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @status generated — ts-api-smoke template (scala-test-suites
|
|
3
|
-
* templates/ts_api_smoke.pkl @1.
|
|
4
|
-
* ./fetch-org-brand declares 1 runtime export(s) and
|
|
3
|
+
* templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
|
|
4
|
+
* ./fetch-org-brand declares 1 runtime export(s) and 6 declared behaviour vector(s);
|
|
5
5
|
* this suite asserts the import resolves, the runtime surface matches
|
|
6
6
|
* exactly, and every export carries its declared kind. Regenerate from
|
|
7
|
-
* inputs/server-service-boot
|
|
7
|
+
* inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
|
|
8
8
|
* overwritten.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -23,26 +23,37 @@ describe("fetch-org-brand (page-org-first wrapper) exports-map smoke", () => {
|
|
|
23
23
|
it("fetchOrgBrandWithFallback is a function", () => {
|
|
24
24
|
expect(typeof mod["fetchOrgBrandWithFallback"]).toBe("function");
|
|
25
25
|
});
|
|
26
|
-
it("a page org with a brand row renders THAT brand, no fallback consulted", async () => {
|
|
26
|
+
it("a page org with a brand row renders THAT brand, no fallback consulted [toBeNull]", async () => {
|
|
27
27
|
const client = { organisation_profile: { findFirst: async ({ select, where }) => (where.id === "org-branded" ? { company_name: "Launcha", brand_primary_color: "#123456" } : null) } };
|
|
28
|
-
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-branded" }, client, params: {} } as never)
|
|
28
|
+
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-branded" }, client, params: {} } as never)
|
|
29
29
|
expect(got).not.toBeNull();
|
|
30
|
-
expect(
|
|
30
|
+
expect(got).not.toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
it("a page org with a brand row renders THAT brand, no fallback consulted [toBe]", async () => {
|
|
33
|
+
const client = { organisation_profile: { findFirst: async ({ select, where }) => (where.id === "org-branded" ? { company_name: "Launcha", brand_primary_color: "#123456" } : null) } };
|
|
34
|
+
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-branded" }, client, params: {} } as never)
|
|
35
|
+
const got2 = ((got as { companyName: string }).companyName);
|
|
36
|
+
expect(got2).toEqual("Launcha");
|
|
31
37
|
});
|
|
32
38
|
it("a page org with no brand falls back to the website owning-org", async () => {
|
|
33
39
|
const client = { organisation_profile: { findFirst: async ({ where }) => (where.id === "org-fallback" ? { company_name: "Website Owner" } : null) } };
|
|
34
|
-
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-page", fallbackOrgId: "org-fallback" }, client, params: {} } as never)
|
|
35
|
-
|
|
40
|
+
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-page", fallbackOrgId: "org-fallback" }, client, params: {} } as never)
|
|
41
|
+
const got2 = ((got as { companyName: string }).companyName);
|
|
42
|
+
expect(got2).toEqual("Website Owner");
|
|
36
43
|
});
|
|
37
|
-
it("a fallback identical to the page org is not consulted twice", async () => {
|
|
44
|
+
it("a fallback identical to the page org is not consulted twice [toBeNull]", async () => {
|
|
38
45
|
let calls = 0; const client = { organisation_profile: { findFirst: async () => { calls++; return null; } } };
|
|
39
|
-
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-x", fallbackOrgId: "org-x" }, client, params: {} } as never)
|
|
46
|
+
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-x", fallbackOrgId: "org-x" }, client, params: {} } as never)
|
|
40
47
|
expect(got).toBeNull();
|
|
41
|
-
|
|
48
|
+
});
|
|
49
|
+
it("a fallback identical to the page org is not consulted twice [toBe]", async () => {
|
|
50
|
+
let calls = 0; const client = { organisation_profile: { findFirst: async () => { calls++; return null; } } };
|
|
51
|
+
const got = await fetchOrgBrandWithFallback({ input: { orgId: "org-x", fallbackOrgId: "org-x" }, client, params: {} } as never)
|
|
52
|
+
expect(calls).toEqual(2);
|
|
42
53
|
});
|
|
43
54
|
it("no orgId anywhere resolves null and names it", async () => {
|
|
44
55
|
const client = { organisation_profile: { findFirst: async () => { throw new Error("must not be called"); } } };
|
|
45
|
-
const got = await fetchOrgBrandWithFallback({ input: {}, client, params: {} } as never)
|
|
56
|
+
const got = await fetchOrgBrandWithFallback({ input: {}, client, params: {} } as never)
|
|
46
57
|
expect(got).toBeNull();
|
|
47
58
|
});
|
|
48
59
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @status generated — ts-api-smoke template (scala-test-suites
|
|
3
|
-
* templates/ts_api_smoke.pkl @1.
|
|
4
|
-
* ./website-by-host-map declares 1 runtime export(s) and
|
|
3
|
+
* templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
|
|
4
|
+
* ./website-by-host-map declares 1 runtime export(s) and 10 declared behaviour vector(s);
|
|
5
5
|
* this suite asserts the import resolves, the runtime surface matches
|
|
6
6
|
* exactly, and every export carries its declared kind. Regenerate from
|
|
7
|
-
* inputs/server-service-boot
|
|
7
|
+
* inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
|
|
8
8
|
* overwritten.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -23,41 +23,57 @@ describe("website-by-host-map exports-map smoke", () => {
|
|
|
23
23
|
it("resolveWebsiteByHostMap is a function", () => {
|
|
24
24
|
expect(typeof mod["resolveWebsiteByHostMap"]).toBe("function");
|
|
25
25
|
});
|
|
26
|
-
it("no host or no hosts_map resolves null without a query", async () => {
|
|
26
|
+
it("no host or no hosts_map resolves null without a query [toBeNull]", async () => {
|
|
27
27
|
let queried = 0; const client = { builda_websites: { findFirst: async () => { queried++; return null; } } };
|
|
28
|
-
const got = await resolveWebsiteByHostMap({ input: {}, client, params: {} } as never)
|
|
28
|
+
const got = await resolveWebsiteByHostMap({ input: {}, client, params: {} } as never)
|
|
29
29
|
expect(got).toBeNull();
|
|
30
|
-
expect(queried).toBe(0);
|
|
31
30
|
});
|
|
32
|
-
it("
|
|
31
|
+
it("no host or no hosts_map resolves null without a query [toBe]", async () => {
|
|
32
|
+
let queried = 0; const client = { builda_websites: { findFirst: async () => { queried++; return null; } } };
|
|
33
|
+
const got = await resolveWebsiteByHostMap({ input: {}, client, params: {} } as never)
|
|
34
|
+
expect(queried).toEqual(0);
|
|
35
|
+
});
|
|
36
|
+
it("a mapped host resolves ITS website with the entry body_path [toBeNull]", async () => {
|
|
33
37
|
const client = { builda_websites: { findFirst: async ({ where }) => (where.slug === "site-launcha" ? { id: "w1", slug: "site-launcha", name: "Launcha" } : null) } };
|
|
34
|
-
const got = await resolveWebsiteByHostMap({ input: { host: "launcha.example", hosts_map: { "launcha.example": { website: "site-launcha", body_path: "/p" } } }, client, params: {} } as never)
|
|
38
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "launcha.example", hosts_map: { "launcha.example": { website: "site-launcha", body_path: "/p" } } }, client, params: {} } as never)
|
|
35
39
|
expect(got).not.toBeNull();
|
|
36
|
-
expect(
|
|
37
|
-
|
|
40
|
+
expect(got).not.toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
it("a mapped host resolves ITS website with the entry body_path [toBe]", async () => {
|
|
43
|
+
const client = { builda_websites: { findFirst: async ({ where }) => (where.slug === "site-launcha" ? { id: "w1", slug: "site-launcha", name: "Launcha" } : null) } };
|
|
44
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "launcha.example", hosts_map: { "launcha.example": { website: "site-launcha", body_path: "/p" } } }, client, params: {} } as never)
|
|
45
|
+
const got2 = ((got as { slug: string }).slug);
|
|
46
|
+
expect(got2).toEqual("site-launcha");
|
|
47
|
+
});
|
|
48
|
+
it("a mapped host resolves ITS website with the entry body_path [toBe]", async () => {
|
|
49
|
+
const client = { builda_websites: { findFirst: async ({ where }) => (where.slug === "site-launcha" ? { id: "w1", slug: "site-launcha", name: "Launcha" } : null) } };
|
|
50
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "launcha.example", hosts_map: { "launcha.example": { website: "site-launcha", body_path: "/p" } } }, client, params: {} } as never)
|
|
51
|
+
const got2 = ((got as { body_path: string }).body_path);
|
|
52
|
+
expect(got2).toEqual("/p");
|
|
38
53
|
});
|
|
39
54
|
it("an unmapped host falls to default_website and :path follows the request path", async () => {
|
|
40
55
|
const client = { builda_websites: { findFirst: async ({ where }) => (where.slug === "site-default" ? { id: "w2", slug: "site-default" } : null) } };
|
|
41
|
-
const got = await resolveWebsiteByHostMap({ input: { host: "other.example", hosts_map: {}, default_website: "site-default", default_body_path: ":path" }, client, params: { path: "/join" } } as never)
|
|
42
|
-
|
|
56
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "other.example", hosts_map: {}, default_website: "site-default", default_body_path: ":path" }, client, params: { path: "/join" } } as never)
|
|
57
|
+
const got2 = ((got as { body_path: string }).body_path);
|
|
58
|
+
expect(got2).toEqual("/join");
|
|
43
59
|
});
|
|
44
60
|
it("an unknown slug in the map resolves null (the lookup is the validation)", async () => {
|
|
45
61
|
const client = { builda_websites: { findFirst: async () => null } };
|
|
46
|
-
const got = await resolveWebsiteByHostMap({ input: { host: "x.example", hosts_map: { "x.example": { website: "nope" } } }, client, params: {} } as never)
|
|
62
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "x.example", hosts_map: { "x.example": { website: "nope" } } }, client, params: {} } as never)
|
|
47
63
|
expect(got).toBeNull();
|
|
48
64
|
});
|
|
49
65
|
it("a mapped host whose client carries no builda_websites is a MISS, not a TypeError (the sign.scala.business 500)", async () => {
|
|
50
66
|
const client = { other_model: {} };
|
|
51
|
-
const got = await resolveWebsiteByHostMap({ input: { host: "sign.scala.business", hosts_map: { "sign.scala.business": { website: "site-sign" } } }, client, params: {} } as never)
|
|
67
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "sign.scala.business", hosts_map: { "sign.scala.business": { website: "site-sign" } } }, client, params: {} } as never)
|
|
52
68
|
expect(got).toBeNull();
|
|
53
69
|
});
|
|
54
70
|
it("an undefined client on a mapped host is a MISS, never a throw", async () => {
|
|
55
|
-
const got = await resolveWebsiteByHostMap({ input: { host: "sign.scala.business", hosts_map: { "sign.scala.business": { website: "site-sign" } } }, client: undefined, params: {} } as never)
|
|
71
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "sign.scala.business", hosts_map: { "sign.scala.business": { website: "site-sign" } } }, client: undefined, params: {} } as never)
|
|
56
72
|
expect(got).toBeNull();
|
|
57
73
|
});
|
|
58
74
|
it("a bound accessor with no findFirst is a MISS, never a throw", async () => {
|
|
59
75
|
const client = { builda_websites: {} };
|
|
60
|
-
const got = await resolveWebsiteByHostMap({ input: { host: "sign.scala.business", hosts_map: { "sign.scala.business": { website: "site-sign" } } }, client, params: {} } as never)
|
|
76
|
+
const got = await resolveWebsiteByHostMap({ input: { host: "sign.scala.business", hosts_map: { "sign.scala.business": { website: "site-sign" } } }, client, params: {} } as never)
|
|
61
77
|
expect(got).toBeNull();
|
|
62
78
|
});
|
|
63
79
|
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { describe, expect, it } from "bun:test";
|
|
11
11
|
import { inlineLogoAsDataUri } from "./error-page-logo.ts";
|
|
12
12
|
import { PrimaryResourceMissingError, buildResourceNotFoundResponse } from "./resource-not-found.ts";
|
|
13
|
-
import { buildWakingPageHtml } from "@teamscala/
|
|
13
|
+
import { buildWakingPageHtml } from "@teamscala/brand/brand/waking-page-html";
|
|
14
14
|
|
|
15
15
|
describe("PrimaryResourceMissingError", () => {
|
|
16
16
|
it("is an Error carrying resourceType + httpStatus", () => {
|
|
@@ -51,6 +51,24 @@ describe("buildResourceNotFoundResponse", () => {
|
|
|
51
51
|
expect(logo).toBe("data:image/png;base64,iVBORw==");
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
it("passes the resolved logo to the waking page instead of its placeholder", async () => {
|
|
55
|
+
const logo = await inlineLogoAsDataUri(
|
|
56
|
+
"/api/assets/c503629a/logos/cropped.png",
|
|
57
|
+
async () => ({
|
|
58
|
+
file() {
|
|
59
|
+
return {
|
|
60
|
+
stat: async () => ({}),
|
|
61
|
+
arrayBuffer: async () => new Uint8Array([137, 80, 78, 71]).buffer,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
const html = buildWakingPageHtml({ logoUrl: logo });
|
|
67
|
+
expect(logo).toStartWith("data:image/png;base64,");
|
|
68
|
+
expect(html).toContain('<img src="data:image/png;base64,iVBORw=="');
|
|
69
|
+
expect(html).not.toContain('class="uif-logo-loading-pulse" aria-hidden="true"');
|
|
70
|
+
});
|
|
71
|
+
|
|
54
72
|
it("returns a 404 Response for a dead link", () => {
|
|
55
73
|
const res = buildResourceNotFoundResponse("project");
|
|
56
74
|
expect(res).toBeInstanceOf(Response);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @status generated — ts-api-smoke template (scala-test-suites
|
|
3
|
-
* templates/ts_api_smoke.pkl @1.
|
|
3
|
+
* templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
|
|
4
4
|
* ./run-boot-hook-list declares 1 runtime export(s) and 4 declared behaviour vector(s);
|
|
5
5
|
* this suite asserts the import resolves, the runtime surface matches
|
|
6
6
|
* exactly, and every export carries its declared kind. Regenerate from
|
|
7
|
-
* inputs/server-service-boot
|
|
7
|
+
* inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
|
|
8
8
|
* overwritten.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -26,22 +26,24 @@ describe("run-boot-hook-list exports-map smoke", () => {
|
|
|
26
26
|
it("declared hooks run in declared order, registered-but-undeclared run after", async () => {
|
|
27
27
|
const ran: string[] = []; const hooks = new Map([["late", async () => { ran.push("late"); }], ["second", async () => { ran.push("second"); }], ["first", async () => { ran.push("first"); }]]);
|
|
28
28
|
const config = { boot_hooks: [{ slug: "first" }, { slug: "second" }] };
|
|
29
|
-
await runBootHookList(config as never, { bootHooks: hooks } as never, "svc-hl")
|
|
29
|
+
await runBootHookList(config as never, { bootHooks: hooks } as never, "svc-hl")
|
|
30
30
|
expect(ran).toEqual(["first", "second", "late"]);
|
|
31
31
|
});
|
|
32
32
|
it("a declared hook with no registered handler is a boot-fatal, not a silent skip", async () => {
|
|
33
33
|
const config = { boot_hooks: [{ slug: "ghost" }] };
|
|
34
|
-
const call = () => runBootHookList(config as never, { bootHooks: new Map() } as never, "svc-hl")
|
|
35
|
-
|
|
34
|
+
const call = () => runBootHookList(config as never, { bootHooks: new Map() } as never, "svc-hl")
|
|
35
|
+
const got = (call);
|
|
36
|
+
expect(() => got()).toThrow("ghost");
|
|
36
37
|
});
|
|
37
38
|
it("a throwing hook is fatal and NAMES the hook", async () => {
|
|
38
39
|
const hooks = new Map([["boom", async () => { throw new Error("disk gone"); }]]);
|
|
39
40
|
const config = { boot_hooks: [{ slug: "boom" }] };
|
|
40
|
-
const call = () => runBootHookList(config as never, { bootHooks: hooks } as never, "svc-hl")
|
|
41
|
-
|
|
41
|
+
const call = () => runBootHookList(config as never, { bootHooks: hooks } as never, "svc-hl")
|
|
42
|
+
const got = (call);
|
|
43
|
+
expect(() => got()).toThrow("boom");
|
|
42
44
|
});
|
|
43
45
|
it("no hooks and no registrations resolves without work", async () => {
|
|
44
|
-
const got = await runBootHookList({} as never, { bootHooks: new Map() } as never, "svc-hl")
|
|
46
|
+
const got = await runBootHookList({} as never, { bootHooks: new Map() } as never, "svc-hl")
|
|
45
47
|
expect(got).toBeUndefined();
|
|
46
48
|
});
|
|
47
49
|
});
|