@monster-orange/den 0.0.0 → 0.2.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.
Files changed (255) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -1
  4. package/bin/den +68 -0
  5. package/docs/distributions.md +499 -0
  6. package/make/demo.mk +82 -0
  7. package/make/den.mk +80 -0
  8. package/make/journeys.mk +57 -0
  9. package/make/local_dev.mk +154 -0
  10. package/make/security.mk +59 -0
  11. package/make/test.mk +405 -0
  12. package/package.json +101 -4
  13. package/scripts/auth-configure.ts +415 -0
  14. package/scripts/heal-config.ts +92 -0
  15. package/scripts/jsonl-playwright-reporter.js +70 -0
  16. package/scripts/manifest-read.cjs +91 -0
  17. package/scripts/setup-1password.ts +570 -0
  18. package/scripts/setup-secrets.ts +149 -0
  19. package/scripts/view-results.js +156 -0
  20. package/service/install.sh +112 -0
  21. package/src/pillars/api/fixtures/index.ts +128 -0
  22. package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
  23. package/src/pillars/api/global-setup.ts +103 -0
  24. package/src/pillars/api/playwright.config.ts +105 -0
  25. package/src/pillars/api/schemas/example-api.json +110 -0
  26. package/src/pillars/api/tests/health/health.spec.ts +19 -0
  27. package/src/pillars/api/tests/health/manifest.yml +6 -0
  28. package/src/pillars/browser/fixtures/index.ts +232 -0
  29. package/src/pillars/browser/fixtures/package.json +10 -0
  30. package/src/pillars/browser/global-setup.ts +140 -0
  31. package/src/pillars/browser/global-teardown.ts +48 -0
  32. package/src/pillars/browser/playwright.config.ts +212 -0
  33. package/src/pillars/browser/setup.ts +219 -0
  34. package/src/pillars/journeys/app-config.ts +237 -0
  35. package/src/pillars/journeys/discover.ts +160 -0
  36. package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
  37. package/src/pillars/journeys/observability/events.ts +288 -0
  38. package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
  39. package/src/pillars/journeys/report/friction.ts +304 -0
  40. package/src/pillars/journeys/report/junit.ts +105 -0
  41. package/src/pillars/journeys/report/render.ts +273 -0
  42. package/src/pillars/journeys/report/results.ts +323 -0
  43. package/src/pillars/journeys/run.ts +673 -0
  44. package/src/pillars/journeys/runner.ts +1025 -0
  45. package/src/pillars/journeys/sandbox.ts +403 -0
  46. package/src/pillars/journeys/spec/loader.ts +152 -0
  47. package/src/pillars/journeys/spec/schema.ts +208 -0
  48. package/src/pillars/journeys/teardown.ts +98 -0
  49. package/src/pillars/journeys/tools/browser-session.ts +219 -0
  50. package/src/pillars/journeys/tools/browser-tool.ts +914 -0
  51. package/src/pillars/journeys/tools/http-tool.ts +564 -0
  52. package/src/pillars/journeys/tools/ledger.ts +278 -0
  53. package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
  54. package/src/pillars/journeys/verify/assertions.ts +502 -0
  55. package/src/pillars/journeys/verify/verdict.ts +71 -0
  56. package/src/pillars/perf/build-report.ts +36 -0
  57. package/src/pillars/perf/k6.config.js +112 -0
  58. package/src/pillars/perf/run.ts +345 -0
  59. package/src/pillars/perf/scenarios/load-profile.js +97 -0
  60. package/src/pillars/perf/staging.ts +73 -0
  61. package/src/pillars/security/discover.ts +124 -0
  62. package/src/pillars/security/findings.ts +151 -0
  63. package/src/pillars/security/plan.ts +355 -0
  64. package/src/pillars/security/run.ts +757 -0
  65. package/src/pillars/smoke/admin-probe.ts +68 -0
  66. package/src/pillars/smoke/base-url.ts +56 -0
  67. package/src/pillars/smoke/cleanup.ts +8 -0
  68. package/src/pillars/smoke/discover.ts +237 -0
  69. package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
  70. package/src/pillars/smoke/fixture-manager.ts +217 -0
  71. package/src/pillars/smoke/fixtures.ts +468 -0
  72. package/src/pillars/smoke/global-setup-all.ts +262 -0
  73. package/src/pillars/smoke/global-setup-global.ts +262 -0
  74. package/src/pillars/smoke/global-setup.ts +273 -0
  75. package/src/pillars/smoke/global-teardown-all.ts +8 -0
  76. package/src/pillars/smoke/global-teardown-global.ts +8 -0
  77. package/src/pillars/smoke/global-teardown.ts +139 -0
  78. package/src/pillars/smoke/playwright-all.config.ts +147 -0
  79. package/src/pillars/smoke/playwright-global.config.ts +141 -0
  80. package/src/pillars/smoke/playwright.config.ts +162 -0
  81. package/src/pillars/smoke/setup-env.ts +132 -0
  82. package/src/pillars/smoke/setup-global.ts +126 -0
  83. package/src/pillars/smoke/setup.ts +115 -0
  84. package/src/pillars/smoke/smoke-repo.ts +16 -0
  85. package/src/pillars/smoke/staging.ts +49 -0
  86. package/src/pillars/suites/run.ts +147 -0
  87. package/src/scripts/configure-aws-profiles.ts +114 -0
  88. package/src/scripts/register-repos.ts +307 -0
  89. package/src/scripts/setup-default-workspace.ts +203 -0
  90. package/src/shared/agent-actor.ts +77 -0
  91. package/src/shared/applications.ts +138 -0
  92. package/src/shared/auth/api-token.ts +416 -0
  93. package/src/shared/auth/auth0-helper.ts +284 -0
  94. package/src/shared/auth/headless-login.ts +265 -0
  95. package/src/shared/auth/jwt-helper.ts +186 -0
  96. package/src/shared/auth/sudo-vault.ts +69 -0
  97. package/src/shared/auth/test-accounts.ts +98 -0
  98. package/src/shared/auth/token-injection.ts +184 -0
  99. package/src/shared/auth/token-providers/auth0.ts +80 -0
  100. package/src/shared/auth/token-providers/index.ts +182 -0
  101. package/src/shared/auth/token-refresh.ts +273 -0
  102. package/src/shared/banner.ts +15 -0
  103. package/src/shared/brand.ts +79 -0
  104. package/src/shared/cli-tabs.ts +131 -0
  105. package/src/shared/constants.ts +128 -0
  106. package/src/shared/den-config.ts +688 -0
  107. package/src/shared/dev-metrics/emit.ts +354 -0
  108. package/src/shared/edge/config.ts +229 -0
  109. package/src/shared/edge/render.ts +221 -0
  110. package/src/shared/edge/runtime.ts +268 -0
  111. package/src/shared/edge/status.ts +74 -0
  112. package/src/shared/edge/types.ts +62 -0
  113. package/src/shared/engine.ts +67 -0
  114. package/src/shared/environment/aws-profiles.ts +49 -0
  115. package/src/shared/environment/env-selector.ts +161 -0
  116. package/src/shared/environment/playwright-env.ts +82 -0
  117. package/src/shared/environment-manager/catalog.ts +77 -0
  118. package/src/shared/environment-manager/clone-guard.ts +163 -0
  119. package/src/shared/environment-manager/dag.ts +286 -0
  120. package/src/shared/environment-manager/default-workspace.ts +182 -0
  121. package/src/shared/environment-manager/derivation/alias.ts +90 -0
  122. package/src/shared/environment-manager/derivation/cache.ts +97 -0
  123. package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
  124. package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
  125. package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
  126. package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
  127. package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
  128. package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
  129. package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
  130. package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
  131. package/src/shared/environment-manager/derivation/scan.ts +160 -0
  132. package/src/shared/environment-manager/derivation/text.ts +40 -0
  133. package/src/shared/environment-manager/derivation/types.ts +69 -0
  134. package/src/shared/environment-manager/health.ts +635 -0
  135. package/src/shared/environment-manager/index.ts +6 -0
  136. package/src/shared/environment-manager/load-applications.ts +108 -0
  137. package/src/shared/environment-manager/log-tail.ts +213 -0
  138. package/src/shared/environment-manager/make-targets.ts +78 -0
  139. package/src/shared/environment-manager/opctl.ts +335 -0
  140. package/src/shared/environment-manager/readiness.ts +93 -0
  141. package/src/shared/environment-manager/register-local.ts +72 -0
  142. package/src/shared/environment-manager/secrets-check.ts +260 -0
  143. package/src/shared/environment-manager/service-container.ts +47 -0
  144. package/src/shared/environment-manager/service-env.ts +110 -0
  145. package/src/shared/environment-manager/service-runner.ts +1603 -0
  146. package/src/shared/environment-manager/sync-targets.ts +66 -0
  147. package/src/shared/environment-manager/up-plan.ts +326 -0
  148. package/src/shared/environment-manager/workspace-manager.ts +905 -0
  149. package/src/shared/environment-manager/workspaces.ts +860 -0
  150. package/src/shared/manifest/deps.ts +27 -0
  151. package/src/shared/manifest/merge-override.ts +75 -0
  152. package/src/shared/manifest/migrate-manifest.ts +101 -0
  153. package/src/shared/manifest/placement.ts +321 -0
  154. package/src/shared/manifest/read-manifest.ts +278 -0
  155. package/src/shared/manifest/schema.ts +313 -0
  156. package/src/shared/manifest/service-links.ts +93 -0
  157. package/src/shared/manifest/validate-layers.ts +229 -0
  158. package/src/shared/manifest/validate-manifest.ts +200 -0
  159. package/src/shared/manifest/workspace-manifests.ts +92 -0
  160. package/src/shared/open-url.ts +122 -0
  161. package/src/shared/org.ts +44 -0
  162. package/src/shared/paths.ts +190 -0
  163. package/src/shared/perf/optional-fields.ts +105 -0
  164. package/src/shared/perf/report-normalizer.ts +404 -0
  165. package/src/shared/perf/report-paths.ts +19 -0
  166. package/src/shared/perf/report-store.ts +110 -0
  167. package/src/shared/perf/report-types.ts +70 -0
  168. package/src/shared/perf/test-paths.ts +25 -0
  169. package/src/shared/process/log-rotation.ts +147 -0
  170. package/src/shared/process/spawn-streaming.ts +229 -0
  171. package/src/shared/product.ts +92 -0
  172. package/src/shared/profile.ts +141 -0
  173. package/src/shared/redact.ts +107 -0
  174. package/src/shared/repo-kind.ts +32 -0
  175. package/src/shared/reporting/jsonl-reporter.ts +131 -0
  176. package/src/shared/reporting/slack-reporter.ts +340 -0
  177. package/src/shared/results/result-reader.ts +266 -0
  178. package/src/shared/results/result-writer.ts +168 -0
  179. package/src/shared/secrets/doppler.ts +126 -0
  180. package/src/shared/secrets/env.ts +48 -0
  181. package/src/shared/secrets/index.ts +197 -0
  182. package/src/shared/secrets/keys.ts +33 -0
  183. package/src/shared/secrets/onepassword.ts +75 -0
  184. package/src/shared/secrets/types.ts +52 -0
  185. package/src/shared/service-url.ts +119 -0
  186. package/src/shared/smoke/branch-resolver.ts +141 -0
  187. package/src/shared/smoke/git-remote.ts +70 -0
  188. package/src/shared/smoke/repo-cloner.ts +151 -0
  189. package/src/shared/stacks/config.ts +137 -0
  190. package/src/shared/stacks/manifest-commands.ts +118 -0
  191. package/src/shared/stacks/render.ts +253 -0
  192. package/src/shared/stacks/runtime.ts +334 -0
  193. package/src/shared/stacks/status.ts +99 -0
  194. package/src/shared/stacks/types.ts +53 -0
  195. package/src/shared/terminal/tabs.ts +212 -0
  196. package/src/shared/test-discovery/module-scanner.ts +569 -0
  197. package/src/shared/types/index.ts +701 -0
  198. package/src/shared/ui-marker.ts +19 -0
  199. package/src/ui/cli/applications-command.ts +148 -0
  200. package/src/ui/cli/config-command.ts +68 -0
  201. package/src/ui/cli/create-main.ts +9 -0
  202. package/src/ui/cli/create.ts +360 -0
  203. package/src/ui/cli/edge-command.ts +81 -0
  204. package/src/ui/cli/flag-parser.ts +246 -0
  205. package/src/ui/cli/graph-command.ts +570 -0
  206. package/src/ui/cli/health-display.ts +63 -0
  207. package/src/ui/cli/index.ts +3742 -0
  208. package/src/ui/cli/init.ts +855 -0
  209. package/src/ui/cli/invocation-target.ts +175 -0
  210. package/src/ui/cli/logs-command.ts +104 -0
  211. package/src/ui/cli/mcp-main.ts +13 -0
  212. package/src/ui/cli/mcp-stdout-guard.ts +10 -0
  213. package/src/ui/cli/module-selection.ts +31 -0
  214. package/src/ui/cli/prompts.ts +767 -0
  215. package/src/ui/cli/results-viewer.ts +436 -0
  216. package/src/ui/cli/secrets-preflight.ts +91 -0
  217. package/src/ui/cli/stack-command.ts +117 -0
  218. package/src/ui/cli/stalled-setup-recovery.ts +129 -0
  219. package/src/ui/cli/test-runner.ts +1319 -0
  220. package/src/ui/cli/web-command.ts +1073 -0
  221. package/src/ui/mcp/api.ts +284 -0
  222. package/src/ui/mcp/guards.ts +141 -0
  223. package/src/ui/mcp/server.ts +147 -0
  224. package/src/ui/mcp/tools.ts +565 -0
  225. package/src/ui/server/audit.ts +71 -0
  226. package/src/ui/server/changelog-parser.ts +105 -0
  227. package/src/ui/server/daemon-reaper.ts +129 -0
  228. package/src/ui/server/editor-manager.ts +301 -0
  229. package/src/ui/server/github-service.ts +223 -0
  230. package/src/ui/server/graph-prefs.ts +89 -0
  231. package/src/ui/server/health-row.ts +15 -0
  232. package/src/ui/server/http-helpers.ts +98 -0
  233. package/src/ui/server/index.ts +3350 -0
  234. package/src/ui/server/log-history.ts +453 -0
  235. package/src/ui/server/perf-report.ts +42 -0
  236. package/src/ui/server/public/environments.html +496 -0
  237. package/src/ui/server/public/favicon.ico +0 -0
  238. package/src/ui/server/public/graph-model.js +416 -0
  239. package/src/ui/server/public/graph.css +976 -0
  240. package/src/ui/server/public/graph.js +986 -0
  241. package/src/ui/server/public/header.js +1373 -0
  242. package/src/ui/server/public/index.html +2595 -0
  243. package/src/ui/server/public/local-dev.html +5344 -0
  244. package/src/ui/server/public/logs.html +1496 -0
  245. package/src/ui/server/public/panel.js +787 -0
  246. package/src/ui/server/public/perf-report.js +359 -0
  247. package/src/ui/server/public/release.html +2111 -0
  248. package/src/ui/server/public/services.html +1103 -0
  249. package/src/ui/server/public/workspace-picker.js +69 -0
  250. package/src/ui/server/readonly-routes.ts +200 -0
  251. package/src/ui/server/release-deploy-session.ts +246 -0
  252. package/src/ui/server/release-service.ts +1125 -0
  253. package/src/ui/server/sudo-store.ts +111 -0
  254. package/src/ui/server/ui-assets.ts +73 -0
  255. package/src/ui/server/workspace-routes.ts +2185 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Integration Test Global Setup
3
+ *
4
+ * Checks API reachability before any tests run.
5
+ * Fails fast with clear, actionable messages so developers immediately
6
+ * know whether the issue is a missing VPN connection, Docker service down,
7
+ * or a network problem — rather than watching all tests fail with cryptic errors.
8
+ *
9
+ * Non-local environments (dev/sandbox/staging/production) use internal VPN URLs
10
+ * (api.internal.ENV.example.dev). Connect to VPN before running integration tests
11
+ * against those environments.
12
+ */
13
+
14
+ import { type FullConfig } from "@playwright/test";
15
+ import { envSelector } from "../../shared/environment/env-selector.js";
16
+ import { getApiToken } from "../../shared/auth/api-token.js";
17
+ import type { Environment } from "../../shared/types/index.js";
18
+
19
+ import { product } from "../../shared/product.js";
20
+ async function globalSetup(_config: FullConfig) {
21
+ const environment = (process.env.E2E_ENVIRONMENT as Environment) || "local";
22
+ const envConfig = await envSelector.loadEnvironment(environment);
23
+
24
+ // BASE_API_URL env var takes precedence (useful for one-off overrides)
25
+ const apiUrl = process.env.BASE_API_URL || envConfig.baseUrls.api;
26
+
27
+ console.log(`\n🔌 Integration API Health Check`);
28
+ console.log(` Environment: ${environment}`);
29
+ console.log(` API URL: ${apiUrl}\n`);
30
+
31
+ // ── Reachability check ──────────────────────────────────────────────────
32
+ const isDockerUrl =
33
+ apiUrl.includes("localhost") || apiUrl.startsWith("http://");
34
+ const needsVpn = apiUrl.includes(".internal.");
35
+
36
+ try {
37
+ // Try /healthz first (standard health endpoint), fall back to root
38
+ let response: Response;
39
+ try {
40
+ response = await fetch(`${apiUrl}/healthz`, {
41
+ method: "GET",
42
+ signal: AbortSignal.timeout(5000),
43
+ });
44
+ } catch {
45
+ response = await fetch(apiUrl, {
46
+ method: "GET",
47
+ signal: AbortSignal.timeout(5000),
48
+ });
49
+ }
50
+
51
+ // Any HTTP response (including 401, 403, 404) means the server is up
52
+ console.log(`✅ API is reachable (status: ${response.status})`);
53
+ } catch (error: unknown) {
54
+ console.error(`❌ Cannot reach API: ${apiUrl}`);
55
+ console.error(` Error: ${(error as Error).message}`);
56
+
57
+ if (isDockerUrl) {
58
+ console.error(`\n💡 API service appears to be down.`);
59
+ console.error(
60
+ ` Start it first (for example: ${product().cli} up, or the API repo's own make up).\n`,
61
+ );
62
+ } else if (needsVpn) {
63
+ console.error(`\n💡 Cannot reach VPN-protected API at ${apiUrl}.`);
64
+ console.error(` Check that your VPN is connected and try again.`);
65
+ console.error(`\n Note: Synthetic (browser) tests do NOT require VPN.`);
66
+ console.error(` Run: make test-syn-all env=${environment}\n`);
67
+ } else {
68
+ console.error(`\n💡 Check network connectivity and try again.\n`);
69
+ }
70
+
71
+ process.exit(1);
72
+ }
73
+
74
+ // ── Pre-warm JWT token cache ─────────────────────────────────────────────
75
+ // Fetch tokens once here so all parallel workers get a cache hit.
76
+ // Without this, workers race to fetch on first run — parallel token mints
77
+ // can hit IdP rate limits or lose the write race.
78
+ console.log(`\n🔑 Warming JWT token cache...`);
79
+ for (const role of ["admin", "user"] as const) {
80
+ try {
81
+ await getApiToken(role, environment);
82
+ } catch (err: unknown) {
83
+ if ((err as Error).message?.includes("No credentials available")) {
84
+ // Credentials not present — unauthenticated tests can still run
85
+ console.warn(
86
+ ` ⚠ No credentials for ${role} — authenticated ${role} tests will fail`,
87
+ );
88
+ console.warn(
89
+ ` eval $(op signin) && make auth-configure (admin) or verify 1Password vault`,
90
+ );
91
+ } else {
92
+ // Token mint failure, IdP misconfiguration, etc. — fail fast
93
+ console.error(
94
+ `\n❌ Failed to fetch ${role} JWT: ${(err as Error).message}\n`,
95
+ );
96
+ process.exit(1);
97
+ }
98
+ }
99
+ }
100
+ console.log();
101
+ }
102
+
103
+ export default globalSetup;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Playwright Configuration for Integration Tests
3
+ * API testing configuration
4
+ */
5
+
6
+ import { defineConfig } from "@playwright/test";
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { getDistRoot } from "../../shared/paths.js";
10
+ import { distPath, enginePath } from "../../shared/engine.js";
11
+
12
+ /**
13
+ * Resolve the API base URL from:
14
+ * 1. BASE_API_URL env var (highest priority — use for VPN environments)
15
+ * 2. Environment config file (config/environments/<env>.json)
16
+ * 3. Fallback to localhost:4000
17
+ *
18
+ * Note: global-setup.ts validates reachability and exits early if VPN is
19
+ * required but BASE_API_URL is not set, so tests never run against a
20
+ * VPN_REQUIRED placeholder URL.
21
+ */
22
+ function getApiBaseUrl(): string {
23
+ if (process.env.BASE_API_URL) return process.env.BASE_API_URL;
24
+
25
+ const env = process.env.E2E_ENVIRONMENT || "local";
26
+ try {
27
+ const configPath = path.join(
28
+ getDistRoot(),
29
+ `config/environments/${env}.json`,
30
+ );
31
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
32
+ const url = config.baseUrls?.api;
33
+ if (url) return url;
34
+ } catch {
35
+ // Config not found — fall through to default
36
+ }
37
+
38
+ return "http://localhost:4000";
39
+ }
40
+
41
+ export default defineConfig({
42
+ testDir: "./tests",
43
+
44
+ // Fail fast: check API reachability + VPN before any tests run
45
+ globalSetup: enginePath("src/pillars/api/global-setup.ts"),
46
+
47
+ // Run tests in files in parallel
48
+ fullyParallel: true,
49
+
50
+ // Fail the build on CI if you accidentally left test.only in the source code
51
+ forbidOnly: !!process.env.CI,
52
+
53
+ // Retry on CI only
54
+ retries: process.env.CI ? 2 : 0,
55
+
56
+ // Opt out of parallel tests on CI
57
+ workers: process.env.CI ? 1 : undefined,
58
+
59
+ // Reporter configuration (use same report folder as synthetic for consolidated view)
60
+ reporter: [
61
+ [
62
+ "html",
63
+ {
64
+ outputFolder:
65
+ process.env.PLAYWRIGHT_HTML_REPORT || distPath("playwright-report"),
66
+ open: "never",
67
+ },
68
+ ],
69
+ ["list"],
70
+ ["junit", { outputFile: distPath("reports", "junit-api-results.xml") }],
71
+ // Note: JSONL reporter not working via config - use manual recording
72
+ // ["../scripts/jsonl-playwright-reporter.js"],
73
+ ],
74
+
75
+ // Shared settings for all the projects below
76
+ use: {
77
+ // Base URL for API requests — resolved from env config or BASE_API_URL override
78
+ baseURL: getApiBaseUrl(),
79
+
80
+ // Extra HTTP headers
81
+ extraHTTPHeaders: {
82
+ Accept: "application/json",
83
+ "Content-Type": "application/json",
84
+ },
85
+
86
+ // Default timeout for API requests
87
+ timeout: 30000,
88
+ },
89
+
90
+ // Configure projects (no browser needed for API tests)
91
+ projects: [
92
+ {
93
+ name: "api",
94
+ use: {},
95
+ },
96
+ ],
97
+
98
+ // Global timeout for each test
99
+ timeout: 60000,
100
+
101
+ // Expect timeout
102
+ expect: {
103
+ timeout: 10000,
104
+ },
105
+ });
@@ -0,0 +1,110 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "title": "Example API",
5
+ "version": "1.0.0",
6
+ "description": "Example OpenAPI spec"
7
+ },
8
+ "components": {
9
+ "schemas": {
10
+ "User": {
11
+ "type": "object",
12
+ "required": ["id", "email", "name"],
13
+ "properties": {
14
+ "id": {
15
+ "type": "string",
16
+ "format": "uuid"
17
+ },
18
+ "email": {
19
+ "type": "string",
20
+ "format": "email"
21
+ },
22
+ "name": {
23
+ "type": "string"
24
+ },
25
+ "createdAt": {
26
+ "type": "string",
27
+ "format": "date-time"
28
+ },
29
+ "roles": {
30
+ "type": "array",
31
+ "items": {
32
+ "type": "string"
33
+ }
34
+ }
35
+ }
36
+ },
37
+ "UserList": {
38
+ "type": "object",
39
+ "required": ["data", "total"],
40
+ "properties": {
41
+ "data": {
42
+ "type": "array",
43
+ "items": {
44
+ "$ref": "#/components/schemas/User"
45
+ }
46
+ },
47
+ "total": {
48
+ "type": "integer",
49
+ "minimum": 0
50
+ },
51
+ "page": {
52
+ "type": "integer",
53
+ "minimum": 1
54
+ },
55
+ "pageSize": {
56
+ "type": "integer",
57
+ "minimum": 1
58
+ }
59
+ }
60
+ },
61
+ "HealthCheck": {
62
+ "type": "object",
63
+ "required": ["status", "timestamp"],
64
+ "properties": {
65
+ "status": {
66
+ "type": "string",
67
+ "enum": ["ok", "degraded", "down"]
68
+ },
69
+ "timestamp": {
70
+ "type": "string",
71
+ "format": "date-time"
72
+ },
73
+ "services": {
74
+ "type": "object",
75
+ "additionalProperties": {
76
+ "type": "object",
77
+ "properties": {
78
+ "status": {
79
+ "type": "string",
80
+ "enum": ["ok", "degraded", "down"]
81
+ },
82
+ "responseTime": {
83
+ "type": "number"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ },
90
+ "Error": {
91
+ "type": "object",
92
+ "required": ["error", "message"],
93
+ "properties": {
94
+ "error": {
95
+ "type": "string"
96
+ },
97
+ "message": {
98
+ "type": "string"
99
+ },
100
+ "statusCode": {
101
+ "type": "integer"
102
+ },
103
+ "details": {
104
+ "type": "object"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * API health — integration test.
3
+ *
4
+ * Unauthenticated probe of the environment's API base URL
5
+ * (config/environments/<env>.json → baseUrls.api). Override the path with
6
+ * E2E_API_HEALTH_PATH when a stack serves health elsewhere.
7
+ */
8
+
9
+ import { test, expect } from "../../fixtures/index.js";
10
+
11
+ const HEALTH_PATH = process.env.E2E_API_HEALTH_PATH ?? "/health";
12
+
13
+ test(`GET ${HEALTH_PATH} responds 2xx`, async ({ apiContext }) => {
14
+ const res = await apiContext.get(HEALTH_PATH);
15
+ expect(
16
+ res.ok(),
17
+ `Expected 2xx, got ${res.status()}: ${await res.text()}`,
18
+ ).toBe(true);
19
+ });
@@ -0,0 +1,6 @@
1
+ name: API Health
2
+ description: Health check endpoints
3
+ icon: 🏥
4
+ tags:
5
+ - smoke
6
+ - health
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Custom Playwright Fixtures
3
+ * Provides environment, credentials, and auth helpers to tests
4
+ */
5
+
6
+ import { test as base, Page } from "@playwright/test";
7
+ import { envSelector } from "../../../shared/environment/env-selector.js";
8
+ import { getCredentials } from "../../../shared/auth/headless-login.js";
9
+ import { Auth0Helper } from "../../../shared/auth/auth0-helper.js";
10
+ import path from "path";
11
+ import { getDistRoot } from "../../../shared/paths.js";
12
+ import type {
13
+ Environment,
14
+ EnvironmentConfig,
15
+ } from "../../../shared/types/index.js";
16
+
17
+ // Extend base test with custom fixtures
18
+ type CustomFixtures = {
19
+ environment: Environment;
20
+ envConfig: EnvironmentConfig;
21
+ credentials: {
22
+ username: string;
23
+ password: string;
24
+ auth0ClientId?: string;
25
+ };
26
+ auth0Helper: Auth0Helper;
27
+ authenticatedPage: Page; // Page with JWT auth injected
28
+ adminPage: Page; // Page authenticated as admin (via JWT)
29
+ userPage: Page; // Page authenticated as regular user (via JWT)
30
+ betaPage: Page; // Page authenticated as the beta (restricted-access) user
31
+ };
32
+
33
+ export const test = base.extend<CustomFixtures>({
34
+ // Environment fixture
35
+ // eslint-disable-next-line no-empty-pattern
36
+ environment: async ({}, use) => {
37
+ // Get environment from env variable or default to 'local'
38
+ const env = (process.env.E2E_ENVIRONMENT as Environment) || "local";
39
+ await use(env);
40
+ },
41
+
42
+ // Environment config fixture
43
+ envConfig: async ({ environment }, use) => {
44
+ const config = await envSelector.loadEnvironment(environment);
45
+ console.log(`[Fixture] Using environment: ${environment}`);
46
+ console.log(`[Fixture] Web URL: ${config.baseUrls.web}`);
47
+ await use(config);
48
+ },
49
+
50
+ // Credentials fixture (optional - for tests that need programmatic auth)
51
+ credentials: async ({ environment }, use) => {
52
+ const creds = await getCredentials("user", environment);
53
+ if (creds) {
54
+ await use({ username: creds.email, password: creds.password });
55
+ } else {
56
+ // No credentials available — tests will use saved auth state
57
+ console.warn(
58
+ "[Fixture] No credentials available - tests will use saved auth state",
59
+ );
60
+ await use({ username: "", password: "" });
61
+ }
62
+ },
63
+
64
+ // Auth0 helper fixture
65
+ auth0Helper: async ({ envConfig }, use) => {
66
+ if (!envConfig.auth0) {
67
+ throw new Error(
68
+ `auth0Helper fixture needs an "auth0" block in config/environments/${envConfig.name}.json`,
69
+ );
70
+ }
71
+ const helper = new Auth0Helper({
72
+ domain: envConfig.auth0.domain,
73
+ clientId: envConfig.auth0.clientId,
74
+ audience: envConfig.auth0.audience,
75
+ });
76
+ await use(helper);
77
+ },
78
+
79
+ // Authenticated page fixture (DEPRECATED - uses old storageState approach)
80
+ // Note: With saved auth states, the page is already authenticated
81
+ // This fixture just navigates to the app and verifies auth
82
+ authenticatedPage: async ({ page, envConfig }, use) => {
83
+ // Navigate to the app - auth state is already loaded from storageState
84
+ await page.goto(envConfig.baseUrls.web);
85
+
86
+ // Give it a moment to load
87
+ await page.waitForTimeout(1000);
88
+
89
+ // Page should be authenticated via saved auth state
90
+ // If not, test will fail (which is expected behavior)
91
+ await use(page);
92
+ },
93
+
94
+ // Admin page fixture - Uses saved browser session
95
+ // Creates a new context with admin storageState
96
+ // Tests focus on functionality, not auth flow
97
+ adminPage: async ({ browser }, use) => {
98
+ const sessionEnv = process.env.E2E_ENVIRONMENT || "local";
99
+ console.log(`[Auth] Using saved admin session (${sessionEnv})`);
100
+
101
+ // Anchored: must read where global-setup.ts (den-root anchored) writes.
102
+ const adminAuthPath = path.resolve(
103
+ getDistRoot(),
104
+ `playwright/.auth/admin-${sessionEnv}.json`,
105
+ );
106
+
107
+ // Create context with admin session
108
+ const context = await browser.newContext({
109
+ storageState: adminAuthPath,
110
+ });
111
+ const page = await context.newPage();
112
+
113
+ // Auto-accept TOU so it doesn't block tests. TOU-specific tests
114
+ // override this with their own page.route() to test the gate flow.
115
+ await page.route("**/api/tou/status*", (route) => {
116
+ route.fulfill({
117
+ status: 200,
118
+ contentType: "application/json",
119
+ body: JSON.stringify({ accepted: true }),
120
+ });
121
+ });
122
+
123
+ // Auto-dismiss changelog modal by setting a high version in localStorage
124
+ // before React boots. The changelog hook reads from localStorage first.
125
+ await page.addInitScript(() => {
126
+ try {
127
+ localStorage.setItem(
128
+ "pref:account:user:changelog.lastSeenVersion",
129
+ JSON.stringify({ _default: "0.2.0" }),
130
+ );
131
+ } catch {
132
+ // localStorage may not be available
133
+ }
134
+ });
135
+
136
+ // Don't navigate here - let the test navigate to its target directly
137
+ await use(page);
138
+ await context.close();
139
+ },
140
+
141
+ // User page fixture - Uses saved browser session
142
+ // Creates a new context with user storageState
143
+ // Tests focus on functionality, not auth flow
144
+ userPage: async ({ browser }, use) => {
145
+ const sessionEnv = process.env.E2E_ENVIRONMENT || "local";
146
+ console.log(`[Auth] Using saved user session (${sessionEnv})`);
147
+
148
+ const userAuthPath = path.resolve(
149
+ getDistRoot(),
150
+ `playwright/.auth/user-${sessionEnv}.json`,
151
+ );
152
+
153
+ // Create context with user session
154
+ const context = await browser.newContext({
155
+ storageState: userAuthPath,
156
+ });
157
+ const page = await context.newPage();
158
+
159
+ // Auto-accept TOU so it doesn't block tests. TOU-specific tests
160
+ // override this with their own page.route() to test the gate flow.
161
+ await page.route("**/api/tou/status*", (route) => {
162
+ route.fulfill({
163
+ status: 200,
164
+ contentType: "application/json",
165
+ body: JSON.stringify({ accepted: true }),
166
+ });
167
+ });
168
+
169
+ // Auto-dismiss changelog modal
170
+ await page.addInitScript(() => {
171
+ try {
172
+ localStorage.setItem(
173
+ "pref:account:user:changelog.lastSeenVersion",
174
+ JSON.stringify({ _default: "0.2.0" }),
175
+ );
176
+ } catch {
177
+ /* localStorage unavailable (e.g. about:blank before first nav) — harmless */
178
+ }
179
+ });
180
+
181
+ // Don't navigate here - let the test navigate to its target directly
182
+ await use(page);
183
+ await context.close();
184
+ },
185
+
186
+ // Beta page fixture - Uses the saved browser session for the beta account
187
+ // (a restricted-access role; what it can reach is up to the app)
188
+ betaPage: async ({ browser }, use) => {
189
+ const sessionEnv = process.env.E2E_ENVIRONMENT || "local";
190
+ console.log(`[Auth] Using saved beta session (${sessionEnv})`);
191
+
192
+ const betaAuthPath = path.resolve(
193
+ getDistRoot(),
194
+ `playwright/.auth/beta-${sessionEnv}.json`,
195
+ );
196
+
197
+ const context = await browser.newContext({
198
+ storageState: betaAuthPath,
199
+ });
200
+ const page = await context.newPage();
201
+
202
+ // Auto-accept TOU for beta users too
203
+ await page.route("**/api/tou/status*", (route) => {
204
+ route.fulfill({
205
+ status: 200,
206
+ contentType: "application/json",
207
+ body: JSON.stringify({ accepted: true }),
208
+ });
209
+ });
210
+
211
+ // Auto-dismiss changelog modal
212
+ await page.addInitScript(() => {
213
+ try {
214
+ localStorage.setItem(
215
+ "pref:account:user:changelog.lastSeenVersion",
216
+ JSON.stringify({ _default: "0.2.0" }),
217
+ );
218
+ } catch {
219
+ /* localStorage unavailable (e.g. about:blank before first nav) — harmless */
220
+ }
221
+ });
222
+
223
+ await use(page);
224
+ await context.close();
225
+ },
226
+ });
227
+
228
+ export { expect } from "@playwright/test";
229
+
230
+ // Screenshots are captured automatically by playwright.config.ts (screenshot: "on").
231
+ // Tests using adminPage/userPage create their own browser contexts — a hook using the
232
+ // bare `page` fixture would capture a different, unauthenticated page. No hook needed.
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@den/test",
3
+ "version": "1.0.0",
4
+ "description": "den synthetic test fixtures",
5
+ "main": "index.ts",
6
+ "exports": {
7
+ ".": "./index.ts"
8
+ },
9
+ "type": "module"
10
+ }