import { describe, expect, test } from "vitest";
import { ConfigProvider, Effect, Layer } from "effect";
import * as Schema from "effect/Schema";
import * as HttpClient from "effect/http/HttpClient";
import * as HttpClientError from "effect/http/HttpClientError";
import * as HttpClientResponse from "effect/http/HttpClientResponse";
import type * as HttpClientRequest from "effect/http/HttpClientRequest";
import { makeApiClient, operationDefinitions } from "./effect.ts";
import {
V1CreateASsoProviderInput,
V1CreateASsoProviderOutput,
V1DeleteASsoProviderOutput,
V1GetASsoProviderOutput,
V1GetDatabaseOpenapiOutput,
V1ListAllSsoProviderOutput,
V1UpdateASsoProviderOutput,
} from "./generated/contracts.ts";
const textDecoder = new TextDecoder();
function httpClientLayer(
handler: (
request: HttpClientRequest.HttpClientRequest,
) => Effect.Effect<HttpClientResponse.HttpClientResponse, HttpClientError.HttpClientError>,
) {
return Layer.succeed(
HttpClient.HttpClient,
HttpClient.make((request) => handler(request)),
);
}
function jsonResponse(
request: HttpClientRequest.HttpClientRequest,
status: number,
body: unknown,
): HttpClientResponse.HttpClientResponse {
return HttpClientResponse.fromWeb(
request,
new Response(JSON.stringify(body), {
status,
headers: {
"content-type": "application/json",
},
}),
);
}
function requestBodyText(request: HttpClientRequest.HttpClientRequest): string {
if (request.body._tag !== "Uint8Array") {
throw new Error(`Expected Uint8Array body, got ${request.body._tag}`);
}
return textDecoder.decode(request.body.body);
}
const config = {
baseUrl: "https://api.supabase.com",
accessToken: "test-token",
userAgent: "supabase-api/test",
} as const;
describe("SSO provider contracts", () => {
const SPARSE_PROVIDER = {
id: "0b0d48f6-878b-4190-88d7-2ca33ed800bc",
saml: {
entity_id: "https://example.com",
metadata_url: "https://example.com",
metadata_xml: '<?xml version="2.0"?>',
attribute_mapping: {},
},
domains: [
{
domain: "example.com",
created_at: "2023-03-28T13:50:14.464Z",
updated_at: "2023-03-28T13:50:14.464Z",
},
],
created_at: "2023-03-28T13:50:14.464Z",
updated_at: "2023-03-28T13:50:14.464Z",
};
const SINGLE_PROVIDER_SCHEMAS = [
V1GetASsoProviderOutput,
V1CreateASsoProviderOutput,
V1UpdateASsoProviderOutput,
V1DeleteASsoProviderOutput,
];
test("decodes SSO provider responses without nested SAML and domain IDs", () => {
expect(() =>
Schema.decodeSync(V1ListAllSsoProviderOutput)({ items: [SPARSE_PROVIDER] }),
).not.toThrow();
for (const schema of SINGLE_PROVIDER_SCHEMAS) {
expect(() => Schema.decodeSync(schema)(SPARSE_PROVIDER)).not.toThrow();
}
});
test("drops nested SAML and domain IDs the spec no longer declares", () => {
const withLegacyIds = {
...SPARSE_PROVIDER,
saml: { ...SPARSE_PROVIDER.saml, id: "8682fcf4-4056-455c-bd93-f33295604929" },
domains: [{ ...SPARSE_PROVIDER.domains[0], id: "9484591c-a203-4500-bea7-d0aaa845e2f5" }],
};
for (const schema of SINGLE_PROVIDER_SCHEMAS) {
const decoded = Schema.decodeSync(schema)(withLegacyIds);
expect(decoded.saml).not.toHaveProperty("id");
expect(decoded.domains?.[0]).not.toHaveProperty("id");
}
});
test("accepts object-valued SSO attribute mapping defaults", () => {
expect(() =>
Schema.decodeSync(V1CreateASsoProviderInput)({
ref: "abcdefghijklmnopqrst",
type: "saml",
attribute_mapping: {
keys: {
role: { default: { department: "engineering" } },
},
},
}),
).not.toThrow();
});
});
describe("database OpenAPI response contract", () => {
test("accepts a normal non-empty OpenAPI document", () => {
expect(() =>
Schema.decodeSync(V1GetDatabaseOpenapiOutput)({
openapi: "3.0.0",
info: { title: "Example", version: "1.0.0" },
paths: {
"/users": {
get: {
responses: { "200": { description: "ok" } },
},
},
},
}),
).not.toThrow();
});
});
describe("makeApiClient", () => {
test("allows raw operations to override generated request headers", () =>
Effect.runPromise(
Effect.gen(function* () {
let accept: string | undefined;
const client = yield* makeApiClient(config).pipe(
Effect.provide(
httpClientLayer((request) => {
accept = request.headers.accept;
return Effect.succeed(jsonResponse(request, 200, {}));
}),
),
);
yield* client.executeRaw(
operationDefinitions.v1GetAFunctionBody,
{
ref: "abcdefghijklmnopqrst",
function_slug: "hello-world",
},
{ headers: { Accept: "multipart/form-data" } },
);
expect(accept).toBe("multipart/form-data");
}),
));
test("forwards per-request options through generated typed methods", () =>
Effect.runPromise(
Effect.gen(function* () {
let seen: { url: string; authorization: string | undefined } | undefined;
const client = yield* makeApiClient(config).pipe(
Effect.provide(
httpClientLayer((request) => {
seen = { url: request.url, authorization: request.headers.authorization };
return Effect.succeed(jsonResponse(request, 200, []));
}),
),
);
yield* client.v1.listAllProjects(
{},
{ baseUrl: "https://api.example.test", accessToken: "per-request-token" },
);
expect(seen).toEqual({
url: "https://api.example.test/v1/projects",
authorization: "Bearer per-request-token",
});
}),
));
test("uses the default API URL when baseUrl is omitted", () =>
Effect.runPromise(
Effect.gen(function* () {
const seenRequests: Array<{ method: string; url: string }> = [];
const client = yield* makeApiClient({ accessToken: "test-token" }).pipe(
Effect.provide(
httpClientLayer((request) => {
seenRequests.push({
method: request.method,
url: request.url,
});
return Effect.succeed(
jsonResponse(request, 200, {
id: "project-id",
ref: "abcdefghijklmnopqrst",
organization_id: "org-id",
organization_slug: "my-org",
name: "project-name",
region: "us-east-1",
created_at: "2026-03-13T12:00:00.000Z",
status: "ACTIVE_HEALTHY",
database: {
host: "db.supabase.internal",
version: "17.0.1",
postgres_engine: "17",
release_channel: "ga",
},
}),
);
}),
),
);
yield* client.v1.getProject({
ref: "abcdefghijklmnopqrst",
});
expect(seenRequests).toEqual([
{
method: "GET",
url: "https://api.supabase.com/v1/projects/abcdefghijklmnopqrst",
},
]);
}),
));
test("reads the access token from the environment when omitted", () =>
Effect.runPromise(
Effect.gen(function* () {
const seenRequests: Array<{ authorization: string | undefined }> = [];
const client = yield* makeApiClient().pipe(
Effect.provide(
Layer.mergeAll(
httpClientLayer((request) => {
seenRequests.push({
authorization: request.headers.authorization,
});
return Effect.succeed(
jsonResponse(request, 200, {
id: "project-id",
ref: "abcdefghijklmnopqrst",
organization_id: "org-id",
organization_slug: "my-org",
name: "project-name",
region: "us-east-1",
created_at: "2026-03-13T12:00:00.000Z",
status: "ACTIVE_HEALTHY",
database: {
host: "db.supabase.internal",
version: "17.0.1",
postgres_engine: "17",
release_channel: "ga",
},
}),
);
}),
ConfigProvider.layer(
ConfigProvider.fromUnknown({
SUPABASE_ACCESS_TOKEN: "env-token",
}),
),
),
),
);
yield* client.v1.getProject({
ref: "abcdefghijklmnopqrst",
});
expect(seenRequests).toEqual([{ authorization: "Bearer env-token" }]);
}),
));
test("passes configured default headers through the facade client", () =>
Effect.runPromise(
Effect.gen(function* () {
const seenRequests: Array<{
command: string | undefined;
commandRunId: string | undefined;
authorization: string | undefined;
}> = [];
const client = yield* makeApiClient({
...config,
headers: {
"X-Supabase-Command": "projects get",
"X-Supabase-Command-Run-ID": "run-456",
},
}).pipe(
Effect.provide(
httpClientLayer((request) => {
seenRequests.push({
command: request.headers["x-supabase-command"],
commandRunId: request.headers["x-supabase-command-run-id"],
authorization: request.headers.authorization,
});
return Effect.succeed(
jsonResponse(request, 200, {
id: "project-id",
ref: "abcdefghijklmnopqrst",
organization_id: "org-id",
organization_slug: "my-org",
name: "project-name",
region: "us-east-1",
created_at: "2026-03-13T12:00:00.000Z",
status: "ACTIVE_HEALTHY",
database: {
host: "db.supabase.internal",
version: "17.0.1",
postgres_engine: "17",
release_channel: "ga",
},
}),
);
}),
),
);
yield* client.v1.getProject({
ref: "abcdefghijklmnopqrst",
});
expect(seenRequests).toEqual([
{
command: "projects get",
commandRunId: "run-456",
authorization: "Bearer test-token",
},
]);
}),
));
test("fails early when no access token is configured", () =>
Effect.runPromise(
Effect.gen(function* () {
const exit = yield* makeApiClient().pipe(
Effect.exit,
Effect.provide(
Layer.mergeAll(
httpClientLayer((request) =>
Effect.succeed(
jsonResponse(request, 200, {
ok: true,
}),
),
),
ConfigProvider.layer(ConfigProvider.fromUnknown({})),
),
),
);
expect(exit._tag).toBe("Failure");
if (exit._tag === "Failure") {
expect(String(exit.cause)).toContain("Missing access token");
}
}),
));
test("returns only versioned methods under the v1 namespace", () =>
Effect.runPromise(
Effect.gen(function* () {
const seenRequests: Array<{ method: string; url: string }> = [];
const client = yield* makeApiClient(config).pipe(
Effect.provide(
httpClientLayer((request) => {
seenRequests.push({
method: request.method,
url: request.url,
});
if (
request.method === "POST" &&
request.url === "https://api.supabase.com/v1/projects"
) {
return Effect.succeed(
jsonResponse(request, 200, {
id: "project-id",
ref: "abcdefghijklmnopqrst",
organization_id: "org-id",
organization_slug: "my-org",
name: "project-name",
region: "us-east-1",
created_at: "2026-03-13T12:00:00.000Z",
status: "ACTIVE_HEALTHY",
}),
);
}
if (
request.method === "GET" &&
request.url === "https://api.supabase.com/v1/projects"
) {
return Effect.succeed(
jsonResponse(request, 200, [
{
id: "project-id",
ref: "abcdefghijklmnopqrst",
organization_id: "org-id",
organization_slug: "my-org",
name: "project-name",
region: "us-east-1",
created_at: "2026-03-13T12:00:00.000Z",
status: "ACTIVE_HEALTHY",
database: {
host: "db.supabase.internal",
version: "17.0.1",
postgres_engine: "17",
release_channel: "ga",
},
},
]),
);
}
return Effect.succeed(
jsonResponse(request, 200, {
id: "project-id",
ref: "abcdefghijklmnopqrst",
organization_id: "org-id",
organization_slug: "my-org",
name: "project-name",
region: "us-east-1",
created_at: "2026-03-13T12:00:00.000Z",
status: "ACTIVE_HEALTHY",
database: {
host: "db.supabase.internal",
version: "17.0.1",
postgres_engine: "17",
release_channel: "ga",
},
}),
);
}),
),
);
expect("createAProject" in client).toBe(false);
expect("getProject" in client).toBe(false);
expect("listAllProjects" in client).toBe(false);
expect(typeof client.v1.createAProject).toBe("function");
expect(typeof client.v1.getProject).toBe("function");
expect(typeof client.v1.listAllProjects).toBe("function");
const created = yield* client.v1.createAProject({
db_pass: "hunter2",
name: "project-name",
organization_slug: "my-org",
});
const project = yield* client.v1.getProject({
ref: "abcdefghijklmnopqrst",
});
const projects = yield* client.v1.listAllProjects();
expect(created.ref).toBe("abcdefghijklmnopqrst");
expect(project.database.host).toBe("db.supabase.internal");
expect(projects).toHaveLength(1);
expect(seenRequests).toEqual([
{
method: "POST",
url: "https://api.supabase.com/v1/projects",
},
{
method: "GET",
url: "https://api.supabase.com/v1/projects/abcdefghijklmnopqrst",
},
{
method: "GET",
url: "https://api.supabase.com/v1/projects",
},
]);
}),
));
test("addresses same-named v1 and v2 operations independently by namespace", () =>
Effect.runPromise(
Effect.gen(function* () {
const seenRequests: Array<{ method: string; url: string }> = [];
const client = yield* makeApiClient(config).pipe(
Effect.provide(
httpClientLayer((request) => {
seenRequests.push({
method: request.method,
url: request.url,
});
if (request.url === "https://api.supabase.com/v1/organizations/my-org/members") {
return Effect.succeed(
jsonResponse(request, 200, [
{
user_id: "user-id",
user_name: "user-name",
role_name: "Owner",
mfa_enabled: false,
avatar_url: null,
},
]),
);
}
return Effect.succeed(
jsonResponse(request, 200, {
data: [],
links: { prev: null, next: null },
}),
);
}),
),
);
expect(typeof client.v1.listOrganizationMembers).toBe("function");
expect(typeof client.v2.listOrganizationMembers).toBe("function");
const v1Members = yield* client.v1.listOrganizationMembers({ slug: "my-org" });
const v2Members = yield* client.v2.listOrganizationMembers({ slug: "my-org" });
expect(v1Members).toEqual([
{
user_id: "user-id",
user_name: "user-name",
role_name: "Owner",
mfa_enabled: false,
avatar_url: null,
},
]);
expect(v2Members.data).toEqual([]);
expect(seenRequests).toEqual([
{
method: "GET",
url: "https://api.supabase.com/v1/organizations/my-org/members",
},
{
method: "GET",
url: "https://api.supabase.com/v2/organizations/my-org/members",
},
]);
}),
));
test("serializes generated binary methods through the effect facade", () =>
Effect.runPromise(
Effect.gen(function* () {
let seenRequest: HttpClientRequest.HttpClientRequest | undefined;
const client = yield* makeApiClient(config).pipe(
Effect.provide(
httpClientLayer((request) => {
seenRequest = request;
return Effect.succeed(
jsonResponse(request, 201, {
id: "function-id",
slug: "demo",
name: "Demo Function",
status: "ACTIVE",
version: 1,
created_at: 1_710_000_000,
updated_at: 1_710_000_001,
verify_jwt: true,
entrypoint_path: "functions/demo/index.ts",
import_map_path: "functions/demo/deno.json",
ezbr_sha256: "abc123",
}),
);
}),
),
);
const body = new Blob(["console.log('blob body');"]);
const result = yield* client.v1.createAFunction({
ref: "abcdefghijklmnopqrst",
slug: "demo",
verify_jwt: true,
entrypoint_path: "functions/demo/index.ts",
body,
});
expect(result.slug).toBe("demo");
expect(seenRequest?.headers["content-type"]).toBe("application/vnd.denoland.eszip");
expect(new URL(seenRequest!.url).pathname).toBe(
"/v1/projects/abcdefghijklmnopqrst/functions",
);
expect(requestBodyText(seenRequest!)).toBe("console.log('blob body');");
}),
));
});