@monster-orange/den 0.0.0-stage → 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 +166 -2
- 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 +104 -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,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,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.
|