414 lines
21 KiB
TypeScript
414 lines
21 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { describe, expect, test } from "vitest";
|
|
import { defaultAppRoleSeeds, defaultPermissionSeedDescriptors } from "@hivegrid/db/permissions-seed.server";
|
|
import { missingReadinessEnv } from "./run-playwright-a11y.mjs";
|
|
|
|
import {
|
|
E2E_READINESS_API_KEY_ALIAS,
|
|
E2E_READINESS_API_LOG_ENDPOINT,
|
|
E2E_READINESS_API_LOG_METHOD,
|
|
E2E_READINESS_ADMIN_EMAIL,
|
|
E2E_READINESS_ADMIN_PASSWORD,
|
|
E2E_READINESS_CHAT_EVENT_STREAM_ID,
|
|
E2E_READINESS_CHAT_ID,
|
|
E2E_READINESS_CHAT_MEMBER_ID,
|
|
E2E_READINESS_CHAT_NAME,
|
|
E2E_READINESS_CHAT_PEER_MEMBER_ID,
|
|
E2E_READINESS_PLUGIN_ID,
|
|
E2E_READINESS_PLUGIN_VERSION,
|
|
E2E_READINESS_WEBHOOK_WORKFLOW_ID,
|
|
E2E_READINESS_WAITING_EXECUTION_ID,
|
|
E2E_READINESS_WAITING_MANUAL_BLOCK_EXECUTION_ID,
|
|
E2E_READINESS_WAITING_WORKFLOW_ID,
|
|
E2E_READINESS_WORKFLOW_EXECUTION_ID,
|
|
E2E_READINESS_WORKFLOW_ID,
|
|
E2E_READINESS_WORKFLOW_LOG_BLOCK_EXECUTION_ID,
|
|
E2E_READINESS_WORKFLOW_OUTBOX_DRAIN_ASSERTION,
|
|
E2E_READINESS_WORKFLOW_OUTBOX_DRAIN_BLOCKER,
|
|
E2E_READINESS_LIFECYCLE_PLUGIN_ID,
|
|
E2E_READINESS_LIFECYCLE_PLUGIN_VERSION,
|
|
E2E_READINESS_NO_TENANT_EMAIL,
|
|
E2E_READINESS_NO_TENANT_PASSWORD,
|
|
E2E_READINESS_PLUGIN_READONLY_EMAIL,
|
|
E2E_READINESS_PLUGIN_READONLY_PASSWORD,
|
|
E2E_READINESS_PLUGIN_READONLY_ROLE_NAME,
|
|
E2E_READINESS_PLATFORM_LEAD_SOURCE_ID,
|
|
E2E_READINESS_PLATFORM_LEAD_SOURCE_KEY,
|
|
E2E_READINESS_PLATFORM_LEAD_SOURCE_NAME,
|
|
E2E_READINESS_PIPELINE_ID,
|
|
E2E_READINESS_PIPELINE_NAME,
|
|
E2E_READINESS_PIPELINE_STAGES,
|
|
E2E_READINESS_ONBOARDING_CODE,
|
|
E2E_READINESS_ONBOARDING_ID,
|
|
E2E_READINESS_ONBOARDING_STEPS,
|
|
E2E_READINESS_SUBSCRIPTION_PLAN_BILLING_PROVIDER_ID,
|
|
E2E_READINESS_SUBSCRIPTION_PRICE_BILLING_PROVIDER_ID,
|
|
E2E_READINESS_TENANT_NAME,
|
|
E2E_READINESS_TENANT_SLUG,
|
|
E2E_READINESS_UPGRADE_PLAN_BILLING_PROVIDER_ID,
|
|
E2E_READINESS_UPGRADE_PRICE_BILLING_PROVIDER_ID,
|
|
E2E_READINESS_USER_EMAIL,
|
|
E2E_READINESS_USER_PASSWORD,
|
|
appFixturePath,
|
|
assertCanRunE2eReadinessSeed,
|
|
buildE2eReadinessEnv,
|
|
buildE2eReadinessEmailSequenceDefinitions,
|
|
buildE2eReadinessLifecyclePluginManifest,
|
|
buildE2eReadinessManualWorkflowDefinition,
|
|
buildE2eReadinessPluginManifest,
|
|
buildE2eReadinessPluginSettingsHtml,
|
|
buildE2eReadinessPlatformLeadSource,
|
|
buildE2eReadinessWaitingWorkflowDefinition,
|
|
buildE2eReadinessWebhookWorkflowDefinition,
|
|
renderE2eReadinessEnvFile,
|
|
} from "./seed-e2e-readiness";
|
|
|
|
describe("seed-e2e-readiness", () => {
|
|
test("uses only the canonical disposable local admin credentials", () => {
|
|
expect(E2E_READINESS_ADMIN_EMAIL).toBe("admin@email.com");
|
|
expect(E2E_READINESS_ADMIN_PASSWORD).toBe("password");
|
|
});
|
|
|
|
test("uses the independent operator identity schema", async () => {
|
|
const source = await readFile(path.resolve(process.cwd(), "scripts/seed-e2e-readiness.ts"), "utf8");
|
|
|
|
expect(source).not.toContain("adminUser.deleteMany({ where: { userId:");
|
|
expect(source).not.toContain("adminUser.upsert({\n where: { userId:");
|
|
expect(source).toContain('adminRole.findUnique({ where: { systemKey: "super_admin" }');
|
|
expect(source).toContain("adminUserRole.create({");
|
|
});
|
|
|
|
test("refuses production and requires an explicit local opt-in", () => {
|
|
expect(() =>
|
|
assertCanRunE2eReadinessSeed({
|
|
DATABASE_URL: "postgresql://localhost/app",
|
|
E2E_LOCAL_READINESS_SEED: "1",
|
|
NODE_ENV: "production",
|
|
})
|
|
).toThrow("production");
|
|
|
|
expect(() =>
|
|
assertCanRunE2eReadinessSeed({
|
|
DATABASE_URL: "postgresql://localhost/app",
|
|
NODE_ENV: "development",
|
|
})
|
|
).toThrow("E2E_LOCAL_READINESS_SEED=1");
|
|
|
|
expect(() =>
|
|
assertCanRunE2eReadinessSeed({
|
|
E2E_LOCAL_READINESS_SEED: "1",
|
|
NODE_ENV: "development",
|
|
})
|
|
).toThrow("DATABASE_URL");
|
|
});
|
|
|
|
test("builds the readiness env contract consumed by the Playwright gate", () => {
|
|
const env = buildE2eReadinessEnv({
|
|
tenantSlug: E2E_READINESS_TENANT_SLUG,
|
|
chatId: E2E_READINESS_CHAT_ID,
|
|
workflowId: "wf_123",
|
|
webhookWorkflowId: "wf_webhook_123",
|
|
waitingWorkflowId: "wf_waiting_123",
|
|
waitingExecutionId: "wfe_waiting_123",
|
|
pluginBasePath: "/tmp/e2e-plugins",
|
|
});
|
|
|
|
expect(env).toMatchObject({
|
|
E2E_TENANT_EMAIL: E2E_READINESS_USER_EMAIL,
|
|
E2E_TENANT_PASSWORD: E2E_READINESS_USER_PASSWORD,
|
|
E2E_NO_TENANT_EMAIL: E2E_READINESS_NO_TENANT_EMAIL,
|
|
E2E_NO_TENANT_PASSWORD: E2E_READINESS_NO_TENANT_PASSWORD,
|
|
E2E_PLUGIN_READONLY_EMAIL: E2E_READINESS_PLUGIN_READONLY_EMAIL,
|
|
E2E_PLUGIN_READONLY_PASSWORD: E2E_READINESS_PLUGIN_READONLY_PASSWORD,
|
|
E2E_TENANT_ACCOUNT_NAME: E2E_READINESS_TENANT_NAME,
|
|
E2E_TENANT_ACCOUNT_SLUG: E2E_READINESS_TENANT_SLUG,
|
|
E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION: "0",
|
|
E2E_TENANT_DASHBOARD_PATH: "/e2e-readiness/dashboard",
|
|
E2E_TENANT_CHAT_PATH: "/e2e-readiness/chat/chat_e2e_readiness",
|
|
E2E_TENANT_SETTINGS_ACCOUNT_PATH: "/e2e-readiness/settings/workspace",
|
|
E2E_TENANT_SETTINGS_PROFILE_PATH: "/e2e-readiness/settings/profile",
|
|
E2E_TENANT_SUBSCRIPTION_PATH: "/e2e-readiness/settings/billing",
|
|
E2E_TENANT_MEMBERS_PATH: "/e2e-readiness/settings/team",
|
|
E2E_TENANT_ROLES_AND_PERMISSIONS_PATH: "/e2e-readiness/settings/team/roles",
|
|
E2E_TENANT_NOTIFICATIONS_PATH: "/e2e-readiness/settings/profile/notifications",
|
|
E2E_TENANT_LOGS_PATH: "/e2e-readiness/settings/compliance",
|
|
E2E_TENANT_LOG_EVENTS_PATH: "/e2e-readiness/settings/compliance/events",
|
|
E2E_TENANT_AUDIT_EXPORT_PATH: "/e2e-readiness/settings/compliance/audit-export",
|
|
E2E_TENANT_PRIVACY_PATH: "/e2e-readiness/settings/profile/data",
|
|
E2E_TENANT_API_PATH: "/e2e-readiness/settings/integrations/api",
|
|
E2E_TENANT_API_KEYS_PATH: "/e2e-readiness/settings/integrations/api/keys",
|
|
E2E_TENANT_API_LOGS_PATH: "/e2e-readiness/settings/integrations/api/logs",
|
|
E2E_TENANT_POLICIES_PATH: "/e2e-readiness/settings/workspace/policies",
|
|
E2E_ADMIN_EMAIL_SEQUENCES_PATH: "/email-sequences",
|
|
E2E_ADMIN_PLATFORM_LEADS_PATH: "/leads",
|
|
E2E_ADMIN_PLATFORM_LEAD_SOURCE_NAME: E2E_READINESS_PLATFORM_LEAD_SOURCE_NAME,
|
|
E2E_PLUGIN_SETTINGS_PATH: "/e2e-readiness/settings/integrations/plugins",
|
|
E2E_WORKFLOW_ENGINE_PATH: "/e2e-readiness/workflow-engine/workflows",
|
|
E2E_WORKFLOW_BUILDER_PATH: "/e2e-readiness/workflow-engine/workflows/wf_123",
|
|
E2E_WORKFLOW_RUN_API_PATH: "/e2e-readiness/workflow-engine/workflows/wf_123/run/api",
|
|
E2E_WORKFLOW_RUN_MANUAL_PATH: "/e2e-readiness/workflow-engine/workflows/wf_123/run/manual",
|
|
E2E_WORKFLOW_WEBHOOK_BUILDER_PATH: "/e2e-readiness/workflow-engine/workflows/wf_webhook_123",
|
|
E2E_WORKFLOW_WEBHOOK_RUN_MANUAL_PATH: "/e2e-readiness/workflow-engine/workflows/wf_webhook_123/run/manual",
|
|
E2E_WORKFLOW_WAITING_BUILDER_PATH: "/e2e-readiness/workflow-engine/workflows/wf_waiting_123",
|
|
E2E_WORKFLOW_WAITING_RUN_MANUAL_PATH: "/e2e-readiness/workflow-engine/workflows/wf_waiting_123/run/manual",
|
|
E2E_WORKFLOW_WAITING_RUN_API_PATH: "/e2e-readiness/workflow-engine/workflows/wf_waiting_123/run/api",
|
|
E2E_WORKFLOW_WAITING_EXECUTIONS_PATH: "/e2e-readiness/workflow-engine/workflows/wf_waiting_123/executions",
|
|
E2E_WORKFLOW_WAITING_EXECUTION_ID: "wfe_waiting_123",
|
|
E2E_ALLOW_CHAT_SEND: "0",
|
|
E2E_ALLOW_WORKFLOW_RUN: "1",
|
|
E2E_ALLOW_WORKFLOW_WEBHOOK_RUN: "0",
|
|
E2E_WORKFLOW_WEBHOOK_TARGET_URL: "",
|
|
E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN: "0",
|
|
E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS: "success",
|
|
E2E_WORKFLOW_OUTBOX_DRAIN_ASSERTION: "operator-gated",
|
|
E2E_WORKFLOW_OUTBOX_DRAIN_BLOCKER:
|
|
"Workflow outbox-drain readiness is operator-gated: set E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1 only when the general worker is running and the seeded manual workflow can reach the expected success execution status.",
|
|
PLUGIN_BINARY_BASE_PATH: "/tmp/e2e-plugins",
|
|
});
|
|
|
|
expect(missingReadinessEnv({ env })).toEqual([]);
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_TENANT_EMAIL="e2e.readiness@example.test"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_NO_TENANT_EMAIL="e2e.no-tenant@example.test"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_NO_TENANT_PASSWORD="password"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_PLUGIN_READONLY_EMAIL="e2e.plugin-readonly@example.test"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_PLUGIN_READONLY_PASSWORD="password"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION="0"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_TENANT_CHAT_PATH="/e2e-readiness/chat/chat_e2e_readiness"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_ALLOW_CHAT_SEND="0"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_TENANT_SETTINGS_ACCOUNT_PATH="/e2e-readiness/settings/workspace"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_TENANT_MEMBERS_PATH="/e2e-readiness/settings/team"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_TENANT_PRIVACY_PATH="/e2e-readiness/settings/profile/data"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_TENANT_API_LOGS_PATH="/e2e-readiness/settings/integrations/api/logs"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_ADMIN_EMAIL_SEQUENCES_PATH="/email-sequences"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_ADMIN_PLATFORM_LEADS_PATH="/leads"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain(`E2E_ADMIN_PLATFORM_LEAD_SOURCE_NAME="${E2E_READINESS_PLATFORM_LEAD_SOURCE_NAME}"`);
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_ALLOW_WORKFLOW_WEBHOOK_RUN="0"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_WORKFLOW_WEBHOOK_TARGET_URL=""');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain(
|
|
'E2E_WORKFLOW_WAITING_EXECUTIONS_PATH="/e2e-readiness/workflow-engine/workflows/wf_waiting_123/executions"'
|
|
);
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_WORKFLOW_WAITING_EXECUTION_ID="wfe_waiting_123"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN="0"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_WORKFLOW_OUTBOX_DRAIN_EXPECTED_STATUS="success"');
|
|
expect(renderE2eReadinessEnvFile(env)).toContain('E2E_WORKFLOW_OUTBOX_DRAIN_ASSERTION="operator-gated"');
|
|
});
|
|
|
|
test("documents workflow outbox-drain readiness as an operator-gated expected-success probe", () => {
|
|
expect(E2E_READINESS_WORKFLOW_OUTBOX_DRAIN_ASSERTION).toBe("operator-gated");
|
|
expect(E2E_READINESS_WORKFLOW_OUTBOX_DRAIN_BLOCKER).toBe(
|
|
"Workflow outbox-drain readiness is operator-gated: set E2E_REQUIRE_WORKFLOW_OUTBOX_DRAIN=1 only when the general worker is running and the seeded manual workflow can reach the expected success execution status."
|
|
);
|
|
});
|
|
|
|
test("defines an active platform-lead source routed to the seeded operator", () => {
|
|
const source = buildE2eReadinessPlatformLeadSource("admin_e2e");
|
|
|
|
expect(source).toEqual({
|
|
id: E2E_READINESS_PLATFORM_LEAD_SOURCE_ID,
|
|
key: E2E_READINESS_PLATFORM_LEAD_SOURCE_KEY,
|
|
displayName: E2E_READINESS_PLATFORM_LEAD_SOURCE_NAME,
|
|
enabled: true,
|
|
defaultAssigneeAdminUserId: "admin_e2e",
|
|
notifyAdminUserIds: ["admin_e2e"],
|
|
rateLimitPerMinute: 60,
|
|
metadata: { fixture: "e2e-readiness" },
|
|
});
|
|
});
|
|
|
|
test("defines deterministic API key and log fixture identifiers", () => {
|
|
expect(E2E_READINESS_API_KEY_ALIAS).toBe("E2E Readiness API Key");
|
|
expect(E2E_READINESS_API_LOG_ENDPOINT).toBe("/v1/e2e-readiness");
|
|
expect(E2E_READINESS_API_LOG_METHOD).toBe("GET");
|
|
});
|
|
|
|
test("defines a deterministic private chat readiness fixture", () => {
|
|
expect(E2E_READINESS_CHAT_ID).toBe("chat_e2e_readiness");
|
|
expect(E2E_READINESS_CHAT_NAME).toBe("E2E Readiness Team Chat");
|
|
expect(E2E_READINESS_CHAT_EVENT_STREAM_ID).toBe("chat_stream_e2e_readiness");
|
|
expect(E2E_READINESS_CHAT_MEMBER_ID).toBe("chat_member_e2e_readiness");
|
|
expect(E2E_READINESS_CHAT_PEER_MEMBER_ID).toBe("chat_peer_member_e2e_readiness");
|
|
});
|
|
|
|
test("defines a deterministic default project pipeline fixture", () => {
|
|
expect(E2E_READINESS_PIPELINE_ID).toBe("pipeline_e2e_readiness");
|
|
expect(E2E_READINESS_PIPELINE_NAME).toBe("E2E Readiness Projects");
|
|
expect(E2E_READINESS_PIPELINE_STAGES).toEqual([
|
|
expect.objectContaining({ id: "pipeline_stage_e2e_readiness_open", stageKind: "offen", winProbability: 20 }),
|
|
expect.objectContaining({ id: "pipeline_stage_e2e_readiness_won", stageKind: "gewonnen", winProbability: 100 }),
|
|
expect.objectContaining({ id: "pipeline_stage_e2e_readiness_lost", stageKind: "verloren", winProbability: 0 }),
|
|
]);
|
|
});
|
|
|
|
test("defines the canonical first-steps fixture required by the onboarding smoke", () => {
|
|
expect(E2E_READINESS_ONBOARDING_ID).toBe("shk-first-steps-v1");
|
|
expect(E2E_READINESS_ONBOARDING_CODE).toBe("shk-first-steps");
|
|
expect(E2E_READINESS_ONBOARDING_STEPS).toEqual([
|
|
{ id: "shk-first-steps-profile-v1", order: 10, block: "profile" },
|
|
{ id: "shk-first-steps-contacts-v1", order: 20, block: "contacts" },
|
|
{ id: "shk-first-steps-projects-v1", order: 30, block: "projects" },
|
|
{ id: "shk-first-steps-billing-v1", order: 40, block: "billing" },
|
|
]);
|
|
});
|
|
|
|
test("defines separate owned and unowned subscription fixtures for billing smokes", () => {
|
|
expect(E2E_READINESS_SUBSCRIPTION_PLAN_BILLING_PROVIDER_ID).toBe("e2e_readiness_plan");
|
|
expect(E2E_READINESS_SUBSCRIPTION_PRICE_BILLING_PROVIDER_ID).toBe("e2e_readiness_price_monthly");
|
|
expect(E2E_READINESS_UPGRADE_PLAN_BILLING_PROVIDER_ID).toBe("e2e_readiness_upgrade_plan");
|
|
expect(E2E_READINESS_UPGRADE_PRICE_BILLING_PROVIDER_ID).toBe("e2e_readiness_upgrade_price_monthly");
|
|
});
|
|
|
|
test("keeps the plugin read-only fixture on the default User app role contract", () => {
|
|
expect(E2E_READINESS_PLUGIN_READONLY_EMAIL).toBe("e2e.plugin-readonly@example.test");
|
|
expect(E2E_READINESS_PLUGIN_READONLY_PASSWORD).toBe("password");
|
|
expect(E2E_READINESS_PLUGIN_READONLY_ROLE_NAME).toBe("User");
|
|
expect(defaultAppRoleSeeds.find((role) => role.name === E2E_READINESS_PLUGIN_READONLY_ROLE_NAME)).toMatchObject({
|
|
type: "app",
|
|
assignToNewUsers: true,
|
|
});
|
|
|
|
expect(defaultPermissionSeedDescriptors.find((permission) => permission.name === "app.plugins.use")?.inRoles).toContain(
|
|
E2E_READINESS_PLUGIN_READONLY_ROLE_NAME
|
|
);
|
|
expect(defaultPermissionSeedDescriptors.find((permission) => permission.name === "app.plugins.manage")?.inRoles).not.toContain(
|
|
E2E_READINESS_PLUGIN_READONLY_ROLE_NAME
|
|
);
|
|
});
|
|
|
|
test("keeps the plugin fixture shaped for consent, revoke, and iframe settings coverage", () => {
|
|
const manifest = buildE2eReadinessPluginManifest();
|
|
|
|
expect(manifest.id).toBe(E2E_READINESS_PLUGIN_ID);
|
|
expect(manifest.version).toBe(E2E_READINESS_PLUGIN_VERSION);
|
|
expect(manifest.capabilities.map((capability) => capability.name)).toEqual(["log", "audit"]);
|
|
expect(manifest.hooks).toEqual([{ name: "workflow.executed", export: "onWorkflowExecuted" }]);
|
|
expect(manifest.ui?.settings?.entry).toBe("settings/index.html");
|
|
expect(manifest.ui?.settings?.sha256).toMatch(/^[a-f0-9]{64}$/u);
|
|
});
|
|
|
|
test("keeps the plugin settings HTML compatible with the hardened iframe protocol", () => {
|
|
const html = buildE2eReadinessPluginSettingsHtml();
|
|
|
|
expect(html).toContain('meta[name="plugin-id"]');
|
|
expect(html).toContain('meta[name="plugin-settings-channel"]');
|
|
expect(html).toContain('meta[name="plugin-settings-protocol"]');
|
|
expect(html).toContain("parent.postMessage");
|
|
expect(html).toContain("type: 'plugin.settings.set'");
|
|
expect(html).toContain("version: protocolVersion");
|
|
expect(html).toContain("pluginId: pluginId");
|
|
expect(html).toContain("channelId: channelId");
|
|
expect(html).toContain("requestId: 'seed-fixture-save'");
|
|
expect(html).toContain("key: 'theme'");
|
|
expect(html).toContain("value: 'ready'");
|
|
expect(html).toContain("<script>");
|
|
expect(html).not.toContain("onclick=");
|
|
});
|
|
|
|
test("keeps a separate plugin fixture for real lifecycle persistence coverage", () => {
|
|
const manifest = buildE2eReadinessLifecyclePluginManifest();
|
|
|
|
expect(manifest.id).toBe(E2E_READINESS_LIFECYCLE_PLUGIN_ID);
|
|
expect(manifest.version).toBe(E2E_READINESS_LIFECYCLE_PLUGIN_VERSION);
|
|
expect(manifest.capabilities.map((capability) => capability.name)).toEqual(["log", "audit"]);
|
|
expect(manifest.hooks).toEqual([{ name: "workflow.executed", export: "onWorkflowExecuted" }]);
|
|
expect(manifest.ui?.settings).toBeUndefined();
|
|
});
|
|
|
|
test("defines deterministic workflow graph fixtures for log-only and webhook readiness", () => {
|
|
expect(E2E_READINESS_WORKFLOW_EXECUTION_ID).toBe("wfe_123");
|
|
expect(E2E_READINESS_WORKFLOW_LOG_BLOCK_EXECUTION_ID).toBe("wfbe_log_123");
|
|
|
|
expect(buildE2eReadinessManualWorkflowDefinition()).toMatchObject({
|
|
id: E2E_READINESS_WORKFLOW_ID,
|
|
blocks: [
|
|
{ key: "manual", type: "manual", isTrigger: true, isBlock: false, input: {} },
|
|
{ key: "log", type: "log", isTrigger: false, isBlock: true, input: { message: "E2E readiness {{$params.message}}" } },
|
|
],
|
|
edges: [{ from: "manual", to: "log" }],
|
|
inputExample: { title: "Readiness payload", input: { message: "ready" } },
|
|
});
|
|
|
|
expect(buildE2eReadinessWebhookWorkflowDefinition()).toMatchObject({
|
|
id: E2E_READINESS_WEBHOOK_WORKFLOW_ID,
|
|
blocks: [
|
|
{ key: "manual", type: "manual", isTrigger: true, isBlock: false, input: {} },
|
|
{
|
|
key: "httpRequest",
|
|
type: "httpRequest",
|
|
isTrigger: false,
|
|
isBlock: true,
|
|
input: {
|
|
url: "{{$params.webhookUrl}}",
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: '{"message":"{{$params.message}}"}',
|
|
throwsError: true,
|
|
},
|
|
},
|
|
{ key: "log", type: "log", isTrigger: false, isBlock: true, input: { message: "Webhook readiness status {{$httpRequest.statusCode}}" } },
|
|
],
|
|
edges: [
|
|
{ from: "manual", to: "httpRequest" },
|
|
{ from: "httpRequest", to: "log" },
|
|
],
|
|
inputExample: {
|
|
title: "Webhook readiness payload",
|
|
input: { webhookUrl: "http://127.0.0.1:1/e2e-readiness-webhook", message: "ready" },
|
|
},
|
|
});
|
|
|
|
expect(E2E_READINESS_WAITING_WORKFLOW_ID).toBe("wf_waiting_123");
|
|
expect(E2E_READINESS_WAITING_EXECUTION_ID).toBe("wfe_waiting_123");
|
|
expect(E2E_READINESS_WAITING_MANUAL_BLOCK_EXECUTION_ID).toBe("wfbe_waiting_manual_123");
|
|
expect(buildE2eReadinessWaitingWorkflowDefinition()).toMatchObject({
|
|
id: E2E_READINESS_WAITING_WORKFLOW_ID,
|
|
blocks: [
|
|
{ key: "manual", type: "manual", isTrigger: true, isBlock: false, input: {} },
|
|
{
|
|
key: "wait",
|
|
type: "waitForInput",
|
|
isTrigger: false,
|
|
isBlock: true,
|
|
input: { title: "E2E approval", placeholder: "Type approval" },
|
|
},
|
|
{
|
|
key: "variable",
|
|
type: "variable",
|
|
isTrigger: false,
|
|
isBlock: true,
|
|
input: { variableName: "approvalMessage", value: "approved {{waitForInput.input.approved}}" },
|
|
},
|
|
{ key: "log", type: "log", isTrigger: false, isBlock: true, input: { message: "continued {{$vars.approvalMessage}}" } },
|
|
],
|
|
edges: [
|
|
{ from: "manual", to: "wait" },
|
|
{ from: "wait", to: "variable" },
|
|
{ from: "variable", to: "log" },
|
|
],
|
|
inputExample: { title: "Waiting readiness payload", input: { input: { approved: false } } },
|
|
});
|
|
});
|
|
|
|
test("defines deterministic email sequence fixtures for admin and trigger readiness", () => {
|
|
const sequences = buildE2eReadinessEmailSequenceDefinitions("tenant_123");
|
|
|
|
expect(sequences).toHaveLength(4);
|
|
expect(sequences.map((sequence) => sequence.id)).toEqual(["seq_e2e_welcome", "seq_e2e_trial_expiring", "seq_e2e_stale_lead", "seq_e2e_customer_welcome"]);
|
|
expect(sequences.map((sequence) => sequence.triggerEvent)).toEqual([
|
|
"tenant.signup",
|
|
"tenant.trial_expiring",
|
|
"lead.stale_60d",
|
|
"lead.converted_to_customer",
|
|
]);
|
|
expect(sequences.every((sequence) => sequence.tenantId === "tenant_123")).toBe(true);
|
|
expect(sequences.every((sequence) => sequence.isEnabled)).toBe(true);
|
|
expect(sequences.every((sequence) => sequence.steps.length === 1)).toBe(true);
|
|
expect(sequences.map((sequence) => sequence.steps[0]?.template)).toEqual(["welcome", "trial-reminder", "re-activation", "generic-step"]);
|
|
});
|
|
|
|
test("normalizes app fixture paths without host assumptions", () => {
|
|
expect(appFixturePath("acme", "dashboard")).toBe("/acme/dashboard");
|
|
expect(appFixturePath("acme", "/settings/subscription")).toBe("/acme/settings/subscription");
|
|
});
|
|
});
|