@monster-orange/den 0.0.0 → 0.1.0
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/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +165 -1
- package/bin/den +61 -0
- package/docs/distributions.md +256 -0
- package/make/demo.mk +82 -0
- package/make/den.mk +80 -0
- package/make/journeys.mk +57 -0
- package/make/local_dev.mk +154 -0
- package/make/security.mk +59 -0
- package/make/test.mk +405 -0
- package/package.json +99 -4
- package/scripts/auth-configure.ts +415 -0
- package/scripts/heal-config.ts +92 -0
- package/scripts/jsonl-playwright-reporter.js +70 -0
- package/scripts/manifest-read.cjs +91 -0
- package/scripts/setup-1password.ts +570 -0
- package/scripts/setup-secrets.ts +149 -0
- package/scripts/view-results.js +156 -0
- package/service/install.sh +112 -0
- package/src/pillars/api/fixtures/index.ts +128 -0
- package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
- package/src/pillars/api/global-setup.ts +103 -0
- package/src/pillars/api/playwright.config.ts +105 -0
- package/src/pillars/api/schemas/example-api.json +110 -0
- package/src/pillars/api/tests/health/health.spec.ts +19 -0
- package/src/pillars/api/tests/health/manifest.yml +6 -0
- package/src/pillars/browser/fixtures/index.ts +232 -0
- package/src/pillars/browser/fixtures/package.json +10 -0
- package/src/pillars/browser/global-setup.ts +140 -0
- package/src/pillars/browser/global-teardown.ts +48 -0
- package/src/pillars/browser/playwright.config.ts +212 -0
- package/src/pillars/browser/setup.ts +219 -0
- package/src/pillars/journeys/app-config.ts +237 -0
- package/src/pillars/journeys/discover.ts +160 -0
- package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
- package/src/pillars/journeys/observability/events.ts +288 -0
- package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
- package/src/pillars/journeys/report/friction.ts +304 -0
- package/src/pillars/journeys/report/junit.ts +105 -0
- package/src/pillars/journeys/report/render.ts +273 -0
- package/src/pillars/journeys/report/results.ts +323 -0
- package/src/pillars/journeys/run.ts +673 -0
- package/src/pillars/journeys/runner.ts +1025 -0
- package/src/pillars/journeys/sandbox.ts +406 -0
- package/src/pillars/journeys/spec/loader.ts +152 -0
- package/src/pillars/journeys/spec/schema.ts +208 -0
- package/src/pillars/journeys/teardown.ts +98 -0
- package/src/pillars/journeys/tools/browser-session.ts +219 -0
- package/src/pillars/journeys/tools/browser-tool.ts +914 -0
- package/src/pillars/journeys/tools/http-tool.ts +564 -0
- package/src/pillars/journeys/tools/ledger.ts +278 -0
- package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
- package/src/pillars/journeys/verify/assertions.ts +502 -0
- package/src/pillars/journeys/verify/verdict.ts +71 -0
- package/src/pillars/perf/build-report.ts +36 -0
- package/src/pillars/perf/k6.config.js +112 -0
- package/src/pillars/perf/run.ts +345 -0
- package/src/pillars/perf/scenarios/load-profile.js +97 -0
- package/src/pillars/perf/staging.ts +73 -0
- package/src/pillars/security/discover.ts +108 -0
- package/src/pillars/security/findings.ts +151 -0
- package/src/pillars/security/plan.ts +355 -0
- package/src/pillars/security/run.ts +729 -0
- package/src/pillars/smoke/admin-probe.ts +68 -0
- package/src/pillars/smoke/base-url.ts +56 -0
- package/src/pillars/smoke/cleanup.ts +8 -0
- package/src/pillars/smoke/discover.ts +237 -0
- package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
- package/src/pillars/smoke/fixture-manager.ts +217 -0
- package/src/pillars/smoke/fixtures.ts +468 -0
- package/src/pillars/smoke/global-setup-all.ts +262 -0
- package/src/pillars/smoke/global-setup-global.ts +262 -0
- package/src/pillars/smoke/global-setup.ts +273 -0
- package/src/pillars/smoke/global-teardown-all.ts +8 -0
- package/src/pillars/smoke/global-teardown-global.ts +8 -0
- package/src/pillars/smoke/global-teardown.ts +139 -0
- package/src/pillars/smoke/playwright-all.config.ts +147 -0
- package/src/pillars/smoke/playwright-global.config.ts +141 -0
- package/src/pillars/smoke/playwright.config.ts +162 -0
- package/src/pillars/smoke/setup-env.ts +132 -0
- package/src/pillars/smoke/setup-global.ts +126 -0
- package/src/pillars/smoke/setup.ts +115 -0
- package/src/pillars/smoke/smoke-repo.ts +16 -0
- package/src/pillars/smoke/staging.ts +49 -0
- package/src/pillars/suites/run.ts +147 -0
- package/src/scripts/configure-aws-profiles.ts +114 -0
- package/src/scripts/register-repos.ts +307 -0
- package/src/scripts/setup-default-workspace.ts +203 -0
- package/src/shared/applications.ts +138 -0
- package/src/shared/auth/api-token.ts +416 -0
- package/src/shared/auth/auth0-helper.ts +284 -0
- package/src/shared/auth/headless-login.ts +265 -0
- package/src/shared/auth/jwt-helper.ts +186 -0
- package/src/shared/auth/sudo-vault.ts +69 -0
- package/src/shared/auth/test-accounts.ts +98 -0
- package/src/shared/auth/token-injection.ts +184 -0
- package/src/shared/auth/token-providers/auth0.ts +80 -0
- package/src/shared/auth/token-providers/index.ts +182 -0
- package/src/shared/auth/token-refresh.ts +273 -0
- package/src/shared/banner.ts +15 -0
- package/src/shared/brand.ts +79 -0
- package/src/shared/cli-tabs.ts +131 -0
- package/src/shared/constants.ts +128 -0
- package/src/shared/den-config.ts +566 -0
- package/src/shared/dev-metrics/emit.ts +354 -0
- package/src/shared/edge/config.ts +229 -0
- package/src/shared/edge/render.ts +221 -0
- package/src/shared/edge/runtime.ts +268 -0
- package/src/shared/edge/types.ts +62 -0
- package/src/shared/engine.ts +67 -0
- package/src/shared/environment/aws-profiles.ts +49 -0
- package/src/shared/environment/env-selector.ts +161 -0
- package/src/shared/environment/playwright-env.ts +82 -0
- package/src/shared/environment-manager/catalog.ts +77 -0
- package/src/shared/environment-manager/clone-guard.ts +163 -0
- package/src/shared/environment-manager/dag.ts +286 -0
- package/src/shared/environment-manager/default-workspace.ts +182 -0
- package/src/shared/environment-manager/derivation/alias.ts +90 -0
- package/src/shared/environment-manager/derivation/cache.ts +97 -0
- package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
- package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
- package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
- package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
- package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
- package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
- package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
- package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
- package/src/shared/environment-manager/derivation/scan.ts +160 -0
- package/src/shared/environment-manager/derivation/text.ts +40 -0
- package/src/shared/environment-manager/derivation/types.ts +69 -0
- package/src/shared/environment-manager/health.ts +589 -0
- package/src/shared/environment-manager/index.ts +6 -0
- package/src/shared/environment-manager/load-applications.ts +78 -0
- package/src/shared/environment-manager/log-tail.ts +212 -0
- package/src/shared/environment-manager/make-targets.ts +78 -0
- package/src/shared/environment-manager/opctl.ts +334 -0
- package/src/shared/environment-manager/readiness.ts +93 -0
- package/src/shared/environment-manager/register-local.ts +72 -0
- package/src/shared/environment-manager/secrets-check.ts +260 -0
- package/src/shared/environment-manager/service-env.ts +110 -0
- package/src/shared/environment-manager/service-runner.ts +1588 -0
- package/src/shared/environment-manager/up-plan.ts +302 -0
- package/src/shared/environment-manager/workspace-manager.ts +905 -0
- package/src/shared/environment-manager/workspaces.ts +860 -0
- package/src/shared/manifest/deps.ts +27 -0
- package/src/shared/manifest/migrate-manifest.ts +101 -0
- package/src/shared/manifest/read-manifest.ts +160 -0
- package/src/shared/manifest/schema.ts +299 -0
- package/src/shared/manifest/service-links.ts +93 -0
- package/src/shared/manifest/validate-manifest.ts +140 -0
- package/src/shared/manifest/workspace-manifests.ts +92 -0
- package/src/shared/open-url.ts +122 -0
- package/src/shared/org.ts +44 -0
- package/src/shared/paths.ts +178 -0
- package/src/shared/perf/optional-fields.ts +105 -0
- package/src/shared/perf/report-normalizer.ts +404 -0
- package/src/shared/perf/report-paths.ts +19 -0
- package/src/shared/perf/report-store.ts +110 -0
- package/src/shared/perf/report-types.ts +70 -0
- package/src/shared/perf/test-paths.ts +25 -0
- package/src/shared/process/log-rotation.ts +147 -0
- package/src/shared/process/spawn-streaming.ts +229 -0
- package/src/shared/product.ts +92 -0
- package/src/shared/profile.ts +141 -0
- package/src/shared/repo-kind.ts +32 -0
- package/src/shared/reporting/jsonl-reporter.ts +131 -0
- package/src/shared/reporting/slack-reporter.ts +340 -0
- package/src/shared/results/result-reader.ts +266 -0
- package/src/shared/results/result-writer.ts +168 -0
- package/src/shared/secrets/doppler.ts +126 -0
- package/src/shared/secrets/env.ts +48 -0
- package/src/shared/secrets/index.ts +197 -0
- package/src/shared/secrets/keys.ts +33 -0
- package/src/shared/secrets/onepassword.ts +75 -0
- package/src/shared/secrets/types.ts +52 -0
- package/src/shared/service-url.ts +119 -0
- package/src/shared/smoke/branch-resolver.ts +141 -0
- package/src/shared/smoke/git-remote.ts +70 -0
- package/src/shared/smoke/repo-cloner.ts +151 -0
- package/src/shared/stacks/config.ts +137 -0
- package/src/shared/stacks/manifest-commands.ts +88 -0
- package/src/shared/stacks/render.ts +253 -0
- package/src/shared/stacks/runtime.ts +334 -0
- package/src/shared/stacks/types.ts +53 -0
- package/src/shared/terminal/tabs.ts +212 -0
- package/src/shared/test-discovery/module-scanner.ts +569 -0
- package/src/shared/types/index.ts +678 -0
- package/src/shared/ui-marker.ts +19 -0
- package/src/ui/cli/applications-command.ts +116 -0
- package/src/ui/cli/config-command.ts +68 -0
- package/src/ui/cli/create-main.ts +9 -0
- package/src/ui/cli/create.ts +360 -0
- package/src/ui/cli/edge-command.ts +91 -0
- package/src/ui/cli/flag-parser.ts +246 -0
- package/src/ui/cli/graph-command.ts +524 -0
- package/src/ui/cli/health-display.ts +63 -0
- package/src/ui/cli/index.ts +3656 -0
- package/src/ui/cli/init.ts +855 -0
- package/src/ui/cli/invocation-target.ts +175 -0
- package/src/ui/cli/logs-command.ts +104 -0
- package/src/ui/cli/module-selection.ts +31 -0
- package/src/ui/cli/prompts.ts +767 -0
- package/src/ui/cli/results-viewer.ts +436 -0
- package/src/ui/cli/secrets-preflight.ts +91 -0
- package/src/ui/cli/stack-command.ts +144 -0
- package/src/ui/cli/stalled-setup-recovery.ts +129 -0
- package/src/ui/cli/test-runner.ts +1312 -0
- package/src/ui/cli/web-command.ts +1073 -0
- package/src/ui/server/changelog-parser.ts +94 -0
- package/src/ui/server/daemon-reaper.ts +129 -0
- package/src/ui/server/editor-manager.ts +301 -0
- package/src/ui/server/github-service.ts +223 -0
- package/src/ui/server/http-helpers.ts +98 -0
- package/src/ui/server/index.ts +3236 -0
- package/src/ui/server/log-history.ts +452 -0
- package/src/ui/server/perf-report.ts +42 -0
- package/src/ui/server/public/environments.html +494 -0
- package/src/ui/server/public/favicon.ico +0 -0
- package/src/ui/server/public/header.js +1371 -0
- package/src/ui/server/public/index.html +2595 -0
- package/src/ui/server/public/local-dev.html +7405 -0
- package/src/ui/server/public/logs.html +1489 -0
- package/src/ui/server/public/perf-report.js +359 -0
- package/src/ui/server/public/release.html +2111 -0
- package/src/ui/server/public/services.html +1098 -0
- package/src/ui/server/public/workspace-picker.js +69 -0
- package/src/ui/server/release-deploy-session.ts +246 -0
- package/src/ui/server/release-service.ts +1125 -0
- package/src/ui/server/sudo-store.ts +111 -0
- package/src/ui/server/workspace-routes.ts +2154 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke Test Global Setup — All Services
|
|
3
|
+
*
|
|
4
|
+
* Used by playwright-all.config.ts when running make test-smk-all.
|
|
5
|
+
* Runs once before any worker starts:
|
|
6
|
+
* 1. Health-checks every service's base URL
|
|
7
|
+
* 2. Pre-warms JWT tokens for all unique auth roles across services
|
|
8
|
+
* 3. Exports tokens to process.env so all workers inherit them
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { type FullConfig } from "@playwright/test";
|
|
12
|
+
import { envSelector } from "../../shared/environment/env-selector.js";
|
|
13
|
+
import { getApiToken } from "../../shared/auth/api-token.js";
|
|
14
|
+
import { findManifest, resolveRepoPath } from "./discover.js";
|
|
15
|
+
import type { Environment } from "../../shared/types/index.js";
|
|
16
|
+
import { assertStorageAdminProbe } from "./admin-probe.js";
|
|
17
|
+
import { resolveSmokeBaseUrlForService } from "./base-url.js";
|
|
18
|
+
import {
|
|
19
|
+
resolveSmokeCleanupFixture,
|
|
20
|
+
resolveSmokeStorageFixture,
|
|
21
|
+
} from "./fixtures.js";
|
|
22
|
+
import { acquireFixture, fixtureInstanceId } from "./cleanup.js";
|
|
23
|
+
import {
|
|
24
|
+
registerSmokeFixtureReleaseHandles,
|
|
25
|
+
smokeFixtureConsumerKey,
|
|
26
|
+
writeSmokeFixtureHandoff,
|
|
27
|
+
type SmokeFixtureHandoffPayload,
|
|
28
|
+
} from "./global-teardown.js";
|
|
29
|
+
import { smokeRepoName } from "./smoke-repo.js";
|
|
30
|
+
|
|
31
|
+
import { product } from "../../shared/product.js";
|
|
32
|
+
async function globalSetupAll(_config: FullConfig) {
|
|
33
|
+
const environment = (process.env.E2E_ENVIRONMENT as Environment) || "local";
|
|
34
|
+
const repoName = smokeRepoName();
|
|
35
|
+
|
|
36
|
+
const envConfig = await envSelector.loadEnvironment(environment);
|
|
37
|
+
|
|
38
|
+
const discovered = findManifest(repoName);
|
|
39
|
+
if (!discovered) {
|
|
40
|
+
console.error(
|
|
41
|
+
`\n❌ No manifest found for repo "${repoName}" in .${product().name}/`,
|
|
42
|
+
);
|
|
43
|
+
console.error(` Run: make smoke-link repo=${repoName}\n`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const smokeServicesFilter = process.env.SMOKE_SERVICES
|
|
48
|
+
? new Set(process.env.SMOKE_SERVICES.split(",").map((s) => s.trim()))
|
|
49
|
+
: null;
|
|
50
|
+
|
|
51
|
+
const allServices = Object.entries(discovered.manifest.smoke ?? {});
|
|
52
|
+
const services = smokeServicesFilter
|
|
53
|
+
? allServices.filter(([n]) => smokeServicesFilter.has(n))
|
|
54
|
+
: allServices;
|
|
55
|
+
|
|
56
|
+
console.log(`\n💨 Smoke Test Setup — All Services`);
|
|
57
|
+
console.log(` Repo: ${repoName}`);
|
|
58
|
+
console.log(` Environment: ${environment}`);
|
|
59
|
+
console.log(` Services: ${services.map(([n]) => n).join(", ")}\n`);
|
|
60
|
+
|
|
61
|
+
// ── Resolve base URLs once per service (used for both reachability + admin probe) ─────────
|
|
62
|
+
const baseUrlByService = new Map(
|
|
63
|
+
services.map(([svcName, svc]) => [
|
|
64
|
+
svcName,
|
|
65
|
+
resolveSmokeBaseUrlForService(
|
|
66
|
+
discovered.manifest,
|
|
67
|
+
repoName,
|
|
68
|
+
svcName,
|
|
69
|
+
svc,
|
|
70
|
+
environment,
|
|
71
|
+
envConfig,
|
|
72
|
+
process.env.BASE_API_URL,
|
|
73
|
+
),
|
|
74
|
+
]),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// ── Reachability check per service ───────────────────────────────────────
|
|
78
|
+
for (const [svcName] of services) {
|
|
79
|
+
const baseUrl = baseUrlByService.get(svcName)!;
|
|
80
|
+
const needsVpn = baseUrl.includes(".internal.");
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
let response: Response;
|
|
84
|
+
try {
|
|
85
|
+
response = await fetch(`${baseUrl}/healthz`, {
|
|
86
|
+
signal: AbortSignal.timeout(5000),
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
response = await fetch(baseUrl, { signal: AbortSignal.timeout(5000) });
|
|
90
|
+
}
|
|
91
|
+
console.log(
|
|
92
|
+
`✅ ${svcName} is reachable at ${baseUrl} (status: ${response.status})`,
|
|
93
|
+
);
|
|
94
|
+
} catch (error: unknown) {
|
|
95
|
+
console.error(`❌ Cannot reach ${svcName}: ${baseUrl}`);
|
|
96
|
+
console.error(` Error: ${(error as Error).message}`);
|
|
97
|
+
if (needsVpn) {
|
|
98
|
+
console.error(
|
|
99
|
+
`\n💡 VPN-protected URL. Check that your VPN is connected.\n`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Storage fixtures: one acquire per repo/service consumer; shared identity → one setup
|
|
107
|
+
const workerCount = _config.workers;
|
|
108
|
+
if (!Number.isInteger(workerCount) || workerCount <= 0) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"Smoke fixture teardown requires a positive Playwright worker count",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const useLocalCheckout = process.env.USE_LOCAL_CHECKOUT === "1";
|
|
114
|
+
const consumersInAcquireOrder: string[] = [];
|
|
115
|
+
const bindings: SmokeFixtureHandoffPayload["bindings"] = [];
|
|
116
|
+
for (const [svcName, svc] of services) {
|
|
117
|
+
if (!svc.fixtures?.storage?.[environment]) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const lifecycle = resolveSmokeStorageFixture(
|
|
121
|
+
discovered.manifest,
|
|
122
|
+
svcName,
|
|
123
|
+
environment,
|
|
124
|
+
);
|
|
125
|
+
if (lifecycle.adminProbePath) {
|
|
126
|
+
await assertStorageAdminProbe({
|
|
127
|
+
baseUrl: baseUrlByService.get(svcName)!,
|
|
128
|
+
adminProbePath: lifecycle.adminProbePath,
|
|
129
|
+
label: `${repoName} / ${svcName}`,
|
|
130
|
+
...(lifecycle.adminProbe403Hint
|
|
131
|
+
? { hintOn403: () => lifecycle.adminProbe403Hint! }
|
|
132
|
+
: {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const cwd = resolveRepoPath(discovered, environment, useLocalCheckout);
|
|
136
|
+
const resolvedBaseUrl = baseUrlByService.get(svcName)!;
|
|
137
|
+
const fixtureEnv = {
|
|
138
|
+
...process.env,
|
|
139
|
+
BASE_API_URL: resolvedBaseUrl,
|
|
140
|
+
SMOKE_BASE_URL: resolvedBaseUrl,
|
|
141
|
+
...lifecycle.envVars,
|
|
142
|
+
SMOKE_WORKER_COUNT: String(workerCount),
|
|
143
|
+
};
|
|
144
|
+
const release = await acquireFixture({
|
|
145
|
+
fixtureKey: lifecycle.fixtureKey,
|
|
146
|
+
resolvedPayload: lifecycle.resolvedPayload as unknown as Record<
|
|
147
|
+
string,
|
|
148
|
+
unknown
|
|
149
|
+
>,
|
|
150
|
+
cwd,
|
|
151
|
+
env: fixtureEnv,
|
|
152
|
+
setup: lifecycle.setup,
|
|
153
|
+
teardown: lifecycle.teardown,
|
|
154
|
+
});
|
|
155
|
+
Object.assign(process.env, fixtureEnv);
|
|
156
|
+
const consumerKey = smokeFixtureConsumerKey(repoName, svcName);
|
|
157
|
+
registerSmokeFixtureReleaseHandles(consumerKey, [release]);
|
|
158
|
+
consumersInAcquireOrder.push(consumerKey);
|
|
159
|
+
bindings.push({
|
|
160
|
+
consumerKey,
|
|
161
|
+
fixtureInstanceId: fixtureInstanceId(
|
|
162
|
+
lifecycle.fixtureKey,
|
|
163
|
+
lifecycle.resolvedPayload as unknown as Record<string, unknown>,
|
|
164
|
+
),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Pre-warm JWT tokens (deduplicated across all services) ───────────────
|
|
169
|
+
const optionalRoles = new Set(["beta", "root_admin"]);
|
|
170
|
+
const allRoles = new Set<"admin" | "user" | "beta" | "root_admin">();
|
|
171
|
+
for (const [, svc] of services) {
|
|
172
|
+
for (const role of svc.auth ?? ["admin", "user"]) {
|
|
173
|
+
allRoles.add(role as "admin" | "user" | "beta" | "root_admin");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (allRoles.size > 0) {
|
|
178
|
+
console.log(`\n🔑 Warming JWT token cache...`);
|
|
179
|
+
for (const role of allRoles) {
|
|
180
|
+
const isOptional = optionalRoles.has(role);
|
|
181
|
+
try {
|
|
182
|
+
const token = await getApiToken(role, environment);
|
|
183
|
+
process.env[`SMOKE_${role.toUpperCase()}_TOKEN`] = token;
|
|
184
|
+
console.log(` ✓ ${role} token ready`);
|
|
185
|
+
} catch (err: unknown) {
|
|
186
|
+
const msg = (err as Error).message ?? String(err);
|
|
187
|
+
if (isOptional) {
|
|
188
|
+
console.warn(
|
|
189
|
+
` ⚠ No token for ${role} — ${role} tests will be skipped`,
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
console.error(`\n❌ Failed to fetch ${role} JWT: ${msg}\n`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Cleanup fixtures (teardown-only sweeps) — after token warming so the
|
|
200
|
+
// teardown env snapshot includes SMOKE_*_TOKEN ────────────────────────────
|
|
201
|
+
for (const [svcName, svc] of services) {
|
|
202
|
+
if (!svc.fixtures?.cleanup?.[environment]) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const cleanupLifecycle = resolveSmokeCleanupFixture(
|
|
206
|
+
discovered.manifest,
|
|
207
|
+
svcName,
|
|
208
|
+
environment,
|
|
209
|
+
);
|
|
210
|
+
if (!cleanupLifecycle) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (cleanupLifecycle.adminProbePath) {
|
|
214
|
+
await assertStorageAdminProbe({
|
|
215
|
+
baseUrl: baseUrlByService.get(svcName)!,
|
|
216
|
+
adminProbePath: cleanupLifecycle.adminProbePath,
|
|
217
|
+
label: `${repoName} / ${svcName}`,
|
|
218
|
+
...(cleanupLifecycle.adminProbe403Hint
|
|
219
|
+
? { hintOn403: () => cleanupLifecycle.adminProbe403Hint! }
|
|
220
|
+
: {}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const cwd = resolveRepoPath(discovered, environment, useLocalCheckout);
|
|
224
|
+
const resolvedBaseUrl = baseUrlByService.get(svcName)!;
|
|
225
|
+
const fixtureEnv = {
|
|
226
|
+
...process.env,
|
|
227
|
+
BASE_API_URL: resolvedBaseUrl,
|
|
228
|
+
SMOKE_BASE_URL: resolvedBaseUrl,
|
|
229
|
+
...cleanupLifecycle.envVars,
|
|
230
|
+
SMOKE_WORKER_COUNT: String(workerCount),
|
|
231
|
+
};
|
|
232
|
+
const release = await acquireFixture({
|
|
233
|
+
fixtureKey: cleanupLifecycle.fixtureKey,
|
|
234
|
+
resolvedPayload: cleanupLifecycle.resolvedPayload,
|
|
235
|
+
cwd,
|
|
236
|
+
env: fixtureEnv,
|
|
237
|
+
setup: cleanupLifecycle.setup,
|
|
238
|
+
teardown: cleanupLifecycle.teardown,
|
|
239
|
+
});
|
|
240
|
+
const consumerKey = smokeFixtureConsumerKey(repoName, svcName);
|
|
241
|
+
registerSmokeFixtureReleaseHandles(consumerKey, [release]);
|
|
242
|
+
consumersInAcquireOrder.push(consumerKey);
|
|
243
|
+
bindings.push({
|
|
244
|
+
consumerKey,
|
|
245
|
+
fixtureInstanceId: fixtureInstanceId(
|
|
246
|
+
cleanupLifecycle.fixtureKey,
|
|
247
|
+
cleanupLifecycle.resolvedPayload,
|
|
248
|
+
),
|
|
249
|
+
});
|
|
250
|
+
console.log(
|
|
251
|
+
`🧹 Cleanup fixture armed for ${repoName}/${svcName} (${environment})`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (consumersInAcquireOrder.length > 0) {
|
|
256
|
+
writeSmokeFixtureHandoff({ consumersInAcquireOrder, bindings });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default globalSetupAll;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke Test Global Setup — All Repos, All Services
|
|
3
|
+
*
|
|
4
|
+
* Used by playwright-global.config.ts (make test-smk-global).
|
|
5
|
+
* Runs once before any worker starts:
|
|
6
|
+
* 1. Health-checks every service's base URL across all registered repos
|
|
7
|
+
* 2. Pre-warms JWT tokens for all unique auth roles
|
|
8
|
+
* 3. Exports tokens to process.env so all workers inherit them
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { type FullConfig } from "@playwright/test";
|
|
12
|
+
import { envSelector } from "../../shared/environment/env-selector.js";
|
|
13
|
+
import { getApiToken } from "../../shared/auth/api-token.js";
|
|
14
|
+
import { discoverRepos, resolveRepoPath } from "./discover.js";
|
|
15
|
+
import type { Environment } from "../../shared/types/index.js";
|
|
16
|
+
import { assertStorageAdminProbe } from "./admin-probe.js";
|
|
17
|
+
import { resolveSmokeBaseUrlForService } from "./base-url.js";
|
|
18
|
+
import {
|
|
19
|
+
resolveSmokeCleanupFixture,
|
|
20
|
+
resolveSmokeStorageFixture,
|
|
21
|
+
} from "./fixtures.js";
|
|
22
|
+
import { acquireFixture, fixtureInstanceId } from "./cleanup.js";
|
|
23
|
+
import {
|
|
24
|
+
registerSmokeFixtureReleaseHandles,
|
|
25
|
+
smokeFixtureConsumerKey,
|
|
26
|
+
writeSmokeFixtureHandoff,
|
|
27
|
+
type SmokeFixtureHandoffPayload,
|
|
28
|
+
} from "./global-teardown.js";
|
|
29
|
+
|
|
30
|
+
async function globalSetupGlobal(_config: FullConfig) {
|
|
31
|
+
const environment = (process.env.E2E_ENVIRONMENT as Environment) || "local";
|
|
32
|
+
const repos = discoverRepos();
|
|
33
|
+
|
|
34
|
+
const envConfig = await envSelector.loadEnvironment(environment);
|
|
35
|
+
|
|
36
|
+
const allServices = repos.flatMap((repo) =>
|
|
37
|
+
Object.entries(repo.manifest.smoke ?? {}).map(([svcName, svc]) => ({
|
|
38
|
+
repo: repo.repoName,
|
|
39
|
+
manifest: repo.manifest,
|
|
40
|
+
svcName,
|
|
41
|
+
svc,
|
|
42
|
+
})),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
console.log(`\n💨 Smoke Test Setup — All Repos`);
|
|
46
|
+
console.log(` Environment: ${environment}`);
|
|
47
|
+
console.log(
|
|
48
|
+
` Services: ${allServices.map((s) => `${s.repo}/${s.svcName}`).join(", ")}\n`,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// ── Resolve base URLs once per service (used for both reachability + admin probe) ─────────
|
|
52
|
+
const baseUrlByService = new Map(
|
|
53
|
+
allServices.map(({ repo, manifest, svcName, svc }) => [
|
|
54
|
+
`${repo}/${svcName}`,
|
|
55
|
+
resolveSmokeBaseUrlForService(
|
|
56
|
+
manifest,
|
|
57
|
+
repo,
|
|
58
|
+
svcName,
|
|
59
|
+
svc,
|
|
60
|
+
environment,
|
|
61
|
+
envConfig,
|
|
62
|
+
process.env.BASE_API_URL,
|
|
63
|
+
),
|
|
64
|
+
]),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// ── Reachability check per service ─────────────────────────────────────────
|
|
68
|
+
for (const { repo, svcName } of allServices) {
|
|
69
|
+
const baseUrl = baseUrlByService.get(`${repo}/${svcName}`)!;
|
|
70
|
+
const needsVpn = baseUrl.includes(".internal.");
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
let response: Response;
|
|
74
|
+
try {
|
|
75
|
+
response = await fetch(`${baseUrl}/healthz`, {
|
|
76
|
+
signal: AbortSignal.timeout(5000),
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
response = await fetch(baseUrl, { signal: AbortSignal.timeout(5000) });
|
|
80
|
+
}
|
|
81
|
+
console.log(
|
|
82
|
+
`✅ ${repo}/${svcName} reachable at ${baseUrl} (${response.status})`,
|
|
83
|
+
);
|
|
84
|
+
} catch (error: unknown) {
|
|
85
|
+
console.error(`❌ Cannot reach ${repo}/${svcName}: ${baseUrl}`);
|
|
86
|
+
console.error(` Error: ${(error as Error).message}`);
|
|
87
|
+
if (needsVpn)
|
|
88
|
+
console.error(`\n💡 VPN-protected URL. Check VPN is connected.\n`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const workerCount = _config.workers;
|
|
94
|
+
if (!Number.isInteger(workerCount) || workerCount <= 0) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
"Smoke fixture teardown requires a positive Playwright worker count",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const useLocalCheckout = process.env.USE_LOCAL_CHECKOUT === "1";
|
|
100
|
+
const repoByName = new Map(repos.map((r) => [r.repoName, r]));
|
|
101
|
+
const consumersInAcquireOrder: string[] = [];
|
|
102
|
+
const bindings: SmokeFixtureHandoffPayload["bindings"] = [];
|
|
103
|
+
|
|
104
|
+
for (const { repo, manifest, svcName, svc } of allServices) {
|
|
105
|
+
if (!svc.fixtures?.storage?.[environment]) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const discoveredRepo = repoByName.get(repo);
|
|
109
|
+
if (!discoveredRepo) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Internal error: repo "${repo}" not in discoverRepos() map`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const lifecycle = resolveSmokeStorageFixture(
|
|
115
|
+
manifest,
|
|
116
|
+
svcName,
|
|
117
|
+
environment,
|
|
118
|
+
);
|
|
119
|
+
if (lifecycle.adminProbePath) {
|
|
120
|
+
await assertStorageAdminProbe({
|
|
121
|
+
baseUrl: baseUrlByService.get(`${repo}/${svcName}`)!,
|
|
122
|
+
adminProbePath: lifecycle.adminProbePath,
|
|
123
|
+
label: `${repo} / ${svcName}`,
|
|
124
|
+
...(lifecycle.adminProbe403Hint
|
|
125
|
+
? { hintOn403: () => lifecycle.adminProbe403Hint! }
|
|
126
|
+
: {}),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const cwd = resolveRepoPath(discoveredRepo, environment, useLocalCheckout);
|
|
130
|
+
const resolvedBaseUrl = baseUrlByService.get(`${repo}/${svcName}`)!;
|
|
131
|
+
const fixtureEnv = {
|
|
132
|
+
...process.env,
|
|
133
|
+
BASE_API_URL: resolvedBaseUrl,
|
|
134
|
+
SMOKE_BASE_URL: resolvedBaseUrl,
|
|
135
|
+
...lifecycle.envVars,
|
|
136
|
+
SMOKE_WORKER_COUNT: String(workerCount),
|
|
137
|
+
};
|
|
138
|
+
const release = await acquireFixture({
|
|
139
|
+
fixtureKey: lifecycle.fixtureKey,
|
|
140
|
+
resolvedPayload: lifecycle.resolvedPayload as unknown as Record<
|
|
141
|
+
string,
|
|
142
|
+
unknown
|
|
143
|
+
>,
|
|
144
|
+
cwd,
|
|
145
|
+
env: fixtureEnv,
|
|
146
|
+
setup: lifecycle.setup,
|
|
147
|
+
teardown: lifecycle.teardown,
|
|
148
|
+
});
|
|
149
|
+
Object.assign(process.env, fixtureEnv);
|
|
150
|
+
const consumerKey = smokeFixtureConsumerKey(repo, svcName);
|
|
151
|
+
registerSmokeFixtureReleaseHandles(consumerKey, [release]);
|
|
152
|
+
consumersInAcquireOrder.push(consumerKey);
|
|
153
|
+
bindings.push({
|
|
154
|
+
consumerKey,
|
|
155
|
+
fixtureInstanceId: fixtureInstanceId(
|
|
156
|
+
lifecycle.fixtureKey,
|
|
157
|
+
lifecycle.resolvedPayload as unknown as Record<string, unknown>,
|
|
158
|
+
),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Pre-warm JWT tokens (deduplicated across all services) ──────────────────
|
|
163
|
+
const optionalRoles = new Set(["beta", "root_admin"]);
|
|
164
|
+
const allRoles = new Set<"admin" | "user" | "beta" | "root_admin">();
|
|
165
|
+
for (const { svc } of allServices) {
|
|
166
|
+
for (const role of svc.auth ?? ["admin", "user"]) {
|
|
167
|
+
allRoles.add(role as "admin" | "user" | "beta" | "root_admin");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (allRoles.size > 0) {
|
|
172
|
+
console.log(`\n🔑 Warming JWT token cache...`);
|
|
173
|
+
for (const role of allRoles) {
|
|
174
|
+
const isOptional = optionalRoles.has(role);
|
|
175
|
+
try {
|
|
176
|
+
const token = await getApiToken(role, environment);
|
|
177
|
+
process.env[`SMOKE_${role.toUpperCase()}_TOKEN`] = token;
|
|
178
|
+
console.log(` ✓ ${role} token ready`);
|
|
179
|
+
} catch (err: unknown) {
|
|
180
|
+
const msg = (err as Error).message ?? String(err);
|
|
181
|
+
if (isOptional) {
|
|
182
|
+
console.warn(
|
|
183
|
+
` ⚠ No token for ${role} — ${role} tests will be skipped`,
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
console.error(`\n❌ Failed to fetch ${role} JWT: ${msg}\n`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Cleanup fixtures (teardown-only sweeps) — after token warming so the
|
|
194
|
+
// teardown env snapshot includes SMOKE_*_TOKEN ──────────────────────────────
|
|
195
|
+
for (const { repo, manifest, svcName, svc } of allServices) {
|
|
196
|
+
if (!svc.fixtures?.cleanup?.[environment]) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const discoveredRepo = repoByName.get(repo);
|
|
200
|
+
if (!discoveredRepo) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Internal error: repo "${repo}" not in discoverRepos() map`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const cleanupLifecycle = resolveSmokeCleanupFixture(
|
|
206
|
+
manifest,
|
|
207
|
+
svcName,
|
|
208
|
+
environment,
|
|
209
|
+
);
|
|
210
|
+
if (!cleanupLifecycle) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (cleanupLifecycle.adminProbePath) {
|
|
214
|
+
await assertStorageAdminProbe({
|
|
215
|
+
baseUrl: baseUrlByService.get(`${repo}/${svcName}`)!,
|
|
216
|
+
adminProbePath: cleanupLifecycle.adminProbePath,
|
|
217
|
+
label: `${repo} / ${svcName}`,
|
|
218
|
+
...(cleanupLifecycle.adminProbe403Hint
|
|
219
|
+
? { hintOn403: () => cleanupLifecycle.adminProbe403Hint! }
|
|
220
|
+
: {}),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const cwd = resolveRepoPath(discoveredRepo, environment, useLocalCheckout);
|
|
224
|
+
const resolvedBaseUrl = baseUrlByService.get(`${repo}/${svcName}`)!;
|
|
225
|
+
const fixtureEnv = {
|
|
226
|
+
...process.env,
|
|
227
|
+
BASE_API_URL: resolvedBaseUrl,
|
|
228
|
+
SMOKE_BASE_URL: resolvedBaseUrl,
|
|
229
|
+
...cleanupLifecycle.envVars,
|
|
230
|
+
SMOKE_WORKER_COUNT: String(workerCount),
|
|
231
|
+
};
|
|
232
|
+
const release = await acquireFixture({
|
|
233
|
+
fixtureKey: cleanupLifecycle.fixtureKey,
|
|
234
|
+
resolvedPayload: cleanupLifecycle.resolvedPayload,
|
|
235
|
+
cwd,
|
|
236
|
+
env: fixtureEnv,
|
|
237
|
+
setup: cleanupLifecycle.setup,
|
|
238
|
+
teardown: cleanupLifecycle.teardown,
|
|
239
|
+
});
|
|
240
|
+
const consumerKey = smokeFixtureConsumerKey(repo, svcName);
|
|
241
|
+
registerSmokeFixtureReleaseHandles(consumerKey, [release]);
|
|
242
|
+
consumersInAcquireOrder.push(consumerKey);
|
|
243
|
+
bindings.push({
|
|
244
|
+
consumerKey,
|
|
245
|
+
fixtureInstanceId: fixtureInstanceId(
|
|
246
|
+
cleanupLifecycle.fixtureKey,
|
|
247
|
+
cleanupLifecycle.resolvedPayload,
|
|
248
|
+
),
|
|
249
|
+
});
|
|
250
|
+
console.log(
|
|
251
|
+
`🧹 Cleanup fixture armed for ${repo}/${svcName} (${environment})`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (consumersInAcquireOrder.length > 0) {
|
|
256
|
+
writeSmokeFixtureHandoff({ consumersInAcquireOrder, bindings });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default globalSetupGlobal;
|