t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/Cloudflare/Zaraz/ZarazConfig.test.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

332 lines
12 KiB
TypeScript

import * as Cloudflare from "@/Cloudflare";
import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
import { findZoneByName } from "@/Cloudflare/Zone/lookup";
import { deepEqual } from "@/Diff";
import * as Provider from "@/Provider";
import { isResourceState, State, type ResourceState } from "@/State";
import * as Test from "@/Test/Alchemy";
import { stripNullFields, stripUndefinedFields } from "@/Util/data";
import * as zaraz from "@distilled.cloud/cloudflare/zaraz";
import { describe, expect } from "alchemy-test";
import * as Effect from "effect/Effect";
import { MinimumLogLevel } from "effect/References";
const { test } = Test.make({ providers: Cloudflare.providers() });
const logLevel = Effect.provideService(
MinimumLogLevel,
process.env.DEBUG ? "Debug" : "Info",
);
const zoneId = process.env.CLOUDFLARE_TEST_ZARAZ_ZONE_ID;
const zoneName =
process.env.CLOUDFLARE_TEST_ZARAZ_ZONE_NAME ?? "alchemy-test-2.us";
// Zaraz needs no entitlement (available on all plans), but its config is a
// zone-wide singleton these tests mutate in place — they only run against a
// dedicated opt-in zone via CLOUDFLARE_TEST_ZARAZ_ZONE_ID (+ optional _NAME).
// All cases mutate the same zone-wide Zaraz config singleton; run them
// serially so they don't corrupt each other under the global concurrent
// test config.
describe.sequential(
"Config",
{ tags: ["provider:cloudflare", "provider:cloudflare:zaraz", "live"] },
() => {
test.provider.skipIf(!zoneId)(
"updates and retains a zone-level Zaraz config",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const original = yield* zaraz.getConfig({ zoneId: zoneId! });
const toggledDataLayer = !original.dataLayer;
yield* Effect.gen(function* () {
const updated = yield* stack.deploy(
Effect.gen(function* () {
return yield* Cloudflare.Zaraz.Config("Config", {
zone: { zoneId: zoneId!, name: zoneName },
dataLayer: toggledDataLayer,
});
}),
);
expect(updated.zoneId).toEqual(zoneId);
expect(updated.dataLayer).toEqual(toggledDataLayer);
const liveUpdated = yield* zaraz.getConfig({ zoneId: zoneId! });
expect(liveUpdated.dataLayer).toEqual(toggledDataLayer);
const restored = yield* stack.deploy(
Effect.gen(function* () {
return yield* Cloudflare.Zaraz.Config("Config", {
zone: { zoneId: zoneId!, name: zoneName },
dataLayer: original.dataLayer,
});
}),
);
expect(restored.dataLayer).toEqual(original.dataLayer);
yield* stack.destroy();
const liveRetained = yield* zaraz.getConfig({ zoneId: zoneId! });
expect(liveRetained.dataLayer).toEqual(original.dataLayer);
}).pipe(
Effect.ensuring(
zaraz
.putConfig(toPutConfig(zoneId!, original))
.pipe(Effect.ignore),
),
);
}).pipe(logLevel),
{ timeout: 120_000 },
);
// Same gate as above: mutates the zone-wide Zaraz singleton, so it requires
// the dedicated opt-in zone from CLOUDFLARE_TEST_ZARAZ_ZONE_ID.
test.provider.skipIf(!zoneId)(
"delete true resets Zaraz config to defaults",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const original = yield* zaraz.getConfig({ zoneId: zoneId! });
const originalWorkflow = yield* zaraz.getWorkflow({
zoneId: zoneId!,
});
const defaults = yield* zaraz.getDefault({ zoneId: zoneId! });
yield* Effect.gen(function* () {
yield* stack.deploy(
Effect.gen(function* () {
return yield* Cloudflare.Zaraz.Config("Config", {
zone: { zoneId: zoneId!, name: zoneName },
dataLayer: !defaults.dataLayer,
workflow: "preview",
delete: true,
});
}),
);
const liveUpdated = yield* zaraz.getConfig({ zoneId: zoneId! });
expect(liveUpdated.dataLayer).toEqual(!defaults.dataLayer);
yield* stack.destroy();
const liveDeleted = yield* zaraz.getConfig({ zoneId: zoneId! });
expect(liveDeleted.dataLayer).toEqual(defaults.dataLayer);
const liveDeletedWorkflow = yield* zaraz.getWorkflow({
zoneId: zoneId!,
});
expect(liveDeletedWorkflow).toEqual("realtime");
}).pipe(
Effect.ensuring(
Effect.gen(function* () {
yield* zaraz
.putConfig(toPutConfig(zoneId!, original))
.pipe(Effect.ignore);
yield* zaraz
.putZaraz({ zoneId: zoneId!, workflow: originalWorkflow })
.pipe(Effect.ignore);
}),
),
);
}).pipe(logLevel),
{ timeout: 120_000 },
);
// Same gate as above: mutates the zone-wide Zaraz singleton, so it requires
// the dedicated opt-in zone from CLOUDFLARE_TEST_ZARAZ_ZONE_ID.
test.provider.skipIf(!zoneId)(
"updates and retains Zaraz workflow mode",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const original = yield* zaraz.getWorkflow({ zoneId: zoneId! });
const workflow = original === "realtime" ? "preview" : "realtime";
yield* Effect.gen(function* () {
const updated = yield* stack.deploy(
Effect.gen(function* () {
return yield* Cloudflare.Zaraz.Config("Config", {
zone: { zoneId: zoneId!, name: zoneName },
workflow,
});
}),
);
expect(updated.workflow).toEqual(workflow);
const liveUpdated = yield* zaraz.getWorkflow({ zoneId: zoneId! });
expect(liveUpdated).toEqual(workflow);
yield* stack.destroy();
const liveRetained = yield* zaraz.getWorkflow({ zoneId: zoneId! });
expect(liveRetained).toEqual(workflow);
}).pipe(
Effect.ensuring(
zaraz
.putZaraz({ zoneId: zoneId!, workflow: original })
.pipe(Effect.ignore),
),
);
}).pipe(logLevel),
{ timeout: 120_000 },
);
// Canonical `list()` test (zone-scoped singleton): there is no account-wide
// API for the per-zone Zaraz config, so `list()` enumerates every zone via
// `listAllZones` and reads the singleton in each. Read-only — asserts the
// result is well-typed, non-empty, and contains the standing test zone.
test.provider(
"list enumerates Zaraz config across all zones",
(stack) =>
Effect.gen(function* () {
const { accountId } = yield* yield* CloudflareEnvironment;
const zone = yield* findZoneByName({ accountId, name: zoneName });
if (!zone) {
return yield* Effect.die(
new Error(`zone "${zoneName}" not found in account`),
);
}
yield* stack.destroy();
const provider = yield* Provider.findProvider(
Cloudflare.Zaraz.Config,
);
const all = yield* provider.list();
expect(all.length).toBeGreaterThan(0);
const row = all.find((c) => c.zoneId === zone.id);
expect(row).toBeDefined();
expect(typeof row!.zoneId).toBe("string");
expect(typeof row!.dataLayer).toBe("boolean");
expect(typeof row!.zarazVersion).toBe("number");
expect(row!.settings).toBeDefined();
expect(row!.workflow).toBeDefined();
yield* stack.destroy();
}).pipe(logLevel),
{ tags: ["provider:cloudflare:zone"], timeout: 120_000 },
);
// Ungated like the `list()` test: the deploy passes NO mutable props, so
// reconcile observes the zone config, computes an identical desired config,
// and skips putConfig/putZaraz entirely — the standing zone's singleton is
// never mutated (capture-and-restore below is just a safety net).
test.provider(
"recovers a half-created config whose creating-state lost Output-valued props (#736)",
(stack) =>
Effect.gen(function* () {
yield* stack.destroy();
const { accountId } = yield* yield* CloudflareEnvironment;
const zone = yield* findZoneByName({ accountId, name: zoneName });
if (!zone) {
return yield* Effect.die(
new Error(`zone "${zoneName}" not found in account`),
);
}
const original = yield* zaraz.getConfig({ zoneId: zone.id });
yield* Effect.gen(function* () {
const deployConfig = () =>
stack.deploy(
Effect.gen(function* () {
// No mutable props: reconcile is a pure observe (no put).
return yield* Cloudflare.Zaraz.Config("Config", {
zone: { zoneId: zone.id, name: zoneName },
});
}),
);
const created = yield* deployConfig();
expect(created.zoneId).toEqual(zone.id);
// Rewrite the persisted row into the wedged shape an interrupted
// deploy leaves behind: `creating`, no attributes, and the
// Output-valued `zone` prop lost in the round-trip (#736).
const state = yield* yield* State;
const stage = stack.stage;
const fqns = yield* state.list({ stack: stack.name, stage });
const rows = yield* Effect.forEach(fqns, (fqn) =>
state
.get({ stack: stack.name, stage, fqn })
.pipe(Effect.map((row) => ({ fqn, row }))),
);
const wedged = rows.find(
(r): r is { fqn: string; row: ResourceState } =>
isResourceState(r.row) &&
r.row.resourceType === "Cloudflare.Zaraz.Config",
);
if (!wedged) {
return yield* Effect.die(
new Error(
"no Cloudflare.Zaraz.Config state row found after deploy",
),
);
}
yield* state.set({
stack: stack.name,
stage,
fqn: wedged.fqn,
value: {
...wedged.row,
status: "creating",
attr: undefined,
props: {
...wedged.row.props,
zone: undefined,
},
},
});
// Before the fix this crashed in read's recovery path with
// `TypeError: undefined is not an object (evaluating 'zone.name')`.
const recovered = yield* deployConfig();
expect(recovered.zoneId).toEqual(created.zoneId);
expect(recovered.dataLayer).toEqual(created.dataLayer);
expect(recovered.workflow).toEqual(created.workflow);
yield* stack.destroy();
}).pipe(
Effect.ensuring(
// Safety net: restore only if something actually changed, so the
// happy path leaves the standing zone's singleton untouched.
Effect.gen(function* () {
const current = yield* zaraz.getConfig({ zoneId: zone.id });
if (!deepEqual(current, original)) {
yield* zaraz.putConfig(toPutConfig(zone.id, original));
}
}).pipe(Effect.ignore),
),
);
}).pipe(logLevel),
{ tags: ["provider:cloudflare:zone"], timeout: 240_000 },
);
},
);
type ConfigResponse = zaraz.GetConfigResponse | zaraz.PutConfigResponse;
const toPutConfig = (
zoneId: string,
config: ConfigResponse,
): zaraz.PutConfigRequest =>
stripUndefinedFields({
zoneId,
dataLayer: config.dataLayer,
debugKey: config.debugKey,
settings: stripNullFields(config.settings),
tools: config.tools,
triggers: config.triggers,
variables: config.variables,
zarazVersion: config.zarazVersion,
analytics: config.analytics ? stripNullFields(config.analytics) : undefined,
consent: config.consent ? stripNullFields(config.consent) : undefined,
historyChange: config.historyChange ?? undefined,
}) as zaraz.PutConfigRequest;