| 1 | import * as AWS from "@/AWS"; |
| 2 | import type { ProviderService } from "@/Provider"; |
| 3 | import * as Test from "@/Test/Alchemy"; |
| 4 | import { expect } from "alchemy-test"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | |
| 7 | const { test } = Test.make({ providers: AWS.providers() }); |
| 8 | |
| 9 | /** |
| 10 | * Dual-mode AWS providers whose LOCAL variant is a process on this machine |
| 11 | * (a dev server), not the floci emulator. They have no emulator API for a |
| 12 | * binding client to route to, so `resolveLocalDataPlane` deliberately falls |
| 13 | * back to the real cloud for them (see Provider.ts). Every other AWS dual |
| 14 | * runs on floci and must say so. |
| 15 | */ |
| 16 | const PROCESS_HOSTED = new Set([ |
| 17 | "Command.Dev", |
| 18 | "AWS.Website.Server", |
| 19 | "Website.Server", |
| 20 | ]); |
| 21 | |
| 22 | /** |
| 23 | * Every floci-backed dual provider must declare the emulator as its |
| 24 | * {@link ProviderService.localDataPlane}. A dual that doesn't is a silent |
| 25 | * hole in `alchemy dev`: a deploy-time binding client bound to it (an |
| 26 | * Action body, a plan-time `execute`) is left unrouted — a single-resource |
| 27 | * binding such as `InvokeFunction(fn)` then targets the real cloud, and a |
| 28 | * binding spanning a declaring sibling (`RunTask(cluster, task)`) dies with |
| 29 | * "Binding client spans mixed data planes" (the reported bug; its e2e is |
| 30 | * test/AWS/Local/Actions.local.test.ts). |
| 31 | * |
| 32 | * `flociDual` declares it for you; the hand-written floci providers |
| 33 | * (`ProviderLayer.dual` with a `Floci*Provider` local) have to say so |
| 34 | * explicitly — this walks the registry and pins that they all do. |
| 35 | */ |
| 36 | test.provider( |
| 37 | "every floci-backed dual AWS provider declares its local data plane", |
| 38 | () => |
| 39 | Effect.gen(function* () { |
| 40 | const context = yield* Effect.context<never>(); |
| 41 | const missing: string[] = []; |
| 42 | let duals = 0; |
| 43 | for (const [key, value] of context.mapUnsafe) { |
| 44 | const entry = value as Partial<ProviderService> & { |
| 45 | kind?: string; |
| 46 | providers?: Record<string, ProviderService>; |
| 47 | }; |
| 48 | // Providers live in context either as individual `Provider` tags or |
| 49 | // inside a `ProviderCollection` keyed by resource type. |
| 50 | const candidates: [string, Partial<ProviderService>][] = |
| 51 | entry.kind === "ProviderCollection" && entry.providers |
| 52 | ? Object.entries(entry.providers) |
| 53 | : typeof entry.reconcile === "function" |
| 54 | ? [[String(key), entry]] |
| 55 | : []; |
| 56 | for (const [type, service] of candidates) { |
| 57 | if (service.modes === undefined || PROCESS_HOSTED.has(type)) continue; |
| 58 | duals += 1; |
| 59 | if (service.localDataPlane === undefined) missing.push(type); |
| 60 | } |
| 61 | } |
| 62 | // Sanity: the registry was actually walked (hundreds of duals). |
| 63 | expect(duals).toBeGreaterThan(100); |
| 64 | expect(missing).toEqual([]); |
| 65 | }), |
| 66 | { tags: ["provider:aws", "local"] }, |
| 67 | ); |