@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.
Files changed (40) hide show
  1. package/package.json +5 -1
  2. package/src/apply-logger-config.test.ts +13 -8
  3. package/src/attach-portal-members.test.ts +4 -0
  4. package/src/boot-shell.test.ts +57 -0
  5. package/src/boot-shell.ts +20 -0
  6. package/src/build-client-assets/sourcemaps-upload.test.ts +51 -6
  7. package/src/error-page-logo.test.ts +35 -15
  8. package/src/mount-orpc-router.test.ts +8 -0
  9. package/src/orpc-contract-rows.test.ts +10 -9
  10. package/src/page-server-loader-graph.test.ts +10 -6
  11. package/src/read-html-template.test.ts +9 -1
  12. package/src/readiness-gate.ts +4 -0
  13. package/src/register-registry-page-route/apply-page-branding.test.ts +13 -1
  14. package/src/register-registry-page-route/apply-page-branding.ts +1 -1
  15. package/src/register-registry-page-route/live-loader-config.test.ts +13 -4
  16. package/src/register-registry-page-route/meta-title.test.ts +8 -8
  17. package/src/register-registry-page-route/page-data-shared.test.ts +12 -5
  18. package/src/register-registry-page-route/resolve-entity-title.test.ts +9 -1
  19. package/src/register-registry-page-route/resolve-page-data.test.ts +16 -1
  20. package/src/register-registry-page-route/resolve-request-graph.test.ts +12 -26
  21. package/src/resolve-brand-favicon.test.ts +13 -11
  22. package/src/resolve-theme-mode.test.ts +52 -27
  23. package/src/resolvers/fetch-org-brand.test.ts +23 -12
  24. package/src/resolvers/website-by-host-map.test.ts +32 -16
  25. package/src/resource-not-found.test.ts +19 -1
  26. package/src/run-boot-hook-list.test.ts +10 -8
  27. package/src/run-boot-hooks.test.ts +11 -25
  28. package/src/scala-tools-currency-watchdog.test.ts +14 -6
  29. package/src/scala-tools-currency-watchdog.ts +5 -1
  30. package/src/theme-fonts-link.test.ts +2 -0
  31. package/src/two-phase-full-app.test.ts +69 -2
  32. package/src/two-phase-full-app.ts +29 -6
  33. package/src/two-phase-health-gate.test.ts +24 -0
  34. package/src/two-phase-minimal-app.test.ts +2 -2
  35. package/src/two-phase-readiness.test.ts +84 -34
  36. package/src/two-phase-readiness.ts +33 -7
  37. package/src/types.test.ts +3 -4
  38. package/src/ui-service.test.ts +2 -2
  39. package/src/ui-service.ts +55 -23
  40. 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.0) over the exports map of @teamscala/service-boot.
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-ts/api_smoke.json (scala-test-suites) — hand edits are
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).toBe("Sign in");
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).toBe("Landing");
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
- getAppLogger: () => ({
15
- warn: (message: string) => warns.push(message),
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
- import { resolveEntityTitle } from "./meta-title.ts";
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
- getAppLogger: () => ({ warn, info: () => {} }),
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
- getAppLogger: () => ({
17
- warn: () => {},
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.0) over the exports map of @teamscala/service-boot.
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-ts/api_smoke.json (scala-test-suites) — hand edits are
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
- import { mock } from "bun:test";
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 caches it", async () => {
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-1");
50
- expect(got).toBe("/api/assets/brand-org-probe-1%2Flogos%2Ffavicon.png");
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.3.0) over the exports map of @teamscala/service-boot.
4
- * ./resolve-theme-mode declares 1 runtime export(s) + 1 type(s) and 5 declared behaviour vector(s);
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-ts/api_smoke.json (scala-test-suites) — hand edits are
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
- expect(got.theme).toBe("dark");
29
- expect(got.embedded).toBe(true);
30
- });
31
- it("an explicit override behaves exactly like the URL form", async () => {
32
- const got = resolveThemeMode({ override: "dark" });
33
- expect(got.theme).toBe("dark");
34
- expect(got.embedded).toBe(true);
35
- });
36
- it("a non-light/dark override is not an override", async () => {
37
- const got = resolveThemeMode({ override: "purple", candidates: ["dark"] });
38
- expect(got.theme).toBe("dark");
39
- expect(got.embedded).toBe(false);
40
- });
41
- it("candidates are consulted in priority order", async () => {
42
- const got = resolveThemeMode({ candidates: [null, "light", "dark"] });
43
- expect(got.theme).toBe("light");
44
- expect(got.embedded).toBe(false);
45
- });
46
- it("nothing resolvable lands on light, never a crash", async () => {
47
- const got = resolveThemeMode({});
48
- expect(got.theme).toBe("light");
49
- expect(got.embedded).toBe(false);
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.3.0) over the exports map of @teamscala/service-boot.
4
- * ./fetch-org-brand declares 1 runtime export(s) and 4 declared behaviour vector(s);
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-ts/api_smoke.json (scala-test-suites) — hand edits are
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((got as { companyName: string }).companyName).toBe("Launcha");
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
- expect((got as { companyName: string }).companyName).toBe("Website Owner");
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
- expect(calls).toBe(2);
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.3.0) over the exports map of @teamscala/service-boot.
4
- * ./website-by-host-map declares 1 runtime export(s) and 7 declared behaviour vector(s);
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-ts/api_smoke.json (scala-test-suites) — hand edits are
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("a mapped host resolves ITS website with the entry body_path", async () => {
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((got as { slug: string }).slug).toBe("site-launcha");
37
- expect((got as { body_path: string }).body_path).toBe("/p");
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
- expect((got as { body_path: string }).body_path).toBe("/join");
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/os/brand/waking-page-html";
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.0) over the exports map of @teamscala/service-boot.
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-ts/api_smoke.json (scala-test-suites) — hand edits are
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
- expect(call).toThrow(/ghost/);
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
- expect(call).toThrow(/boom/);
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
  });