import { describe, expect, test } from "bun:test"; import type { ComposeSpecification, Service } from "../../schema/docker.d"; import { serviceToDeployment, volumesToPvc } from "../../lib/convert"; import { getS3UiUrl } from "../../command/s3"; import { getComposeS3Claims, getServiceS3Claim, isS3VolumeEntry, } from "../../lib/storage"; describe("managed S3 claims", () => { test("uses the same key and bucket for the short syntax", () => { expect( getServiceS3Claim("app", { volumes: ["s3:assets"] } as Service), ).toEqual({ service: "app", key: "assets", bucket: "assets" }); }); test("supports explicit key and bucket names", () => { expect( getServiceS3Claim("app", { volumes: ["s3:app-key/shared-assets"], } as Service), ).toEqual({ service: "app", key: "app-key", bucket: "shared-assets" }); }); test.each([ "s3:", "s3:key/", "s3:/bucket", "s3:key/bucket/extra", "s3:key:bucket", ])("rejects malformed declaration %s", (entry) => { expect(() => getServiceS3Claim("app", { volumes: [entry] } as Service), ).toThrow("Use s3: or s3:/"); }); test("ignores unrelated and long-form volumes", () => { const service = { volumes: ["cache:/cache", { type: "volume", target: "/data" }], } as Service; expect(getServiceS3Claim("app", service)).toBeUndefined(); expect(isS3VolumeEntry("cache:/cache")).toBe(false); }); test("rejects multiple declarations on one service", () => { expect(() => getServiceS3Claim("app", { volumes: ["s3:one", "s3:two"], } as Service), ).toThrow("declares multiple S3 volumes"); }); test("allows services to share the same key and bucket", () => { const compose = { services: { app: { volumes: ["s3:shared/assets"] }, worker: { volumes: ["s3:shared/assets"] }, }, } as ComposeSpecification; expect(getComposeS3Claims(compose)).toHaveLength(2); }); test("rejects one key targeting different buckets", () => { const compose = { services: { app: { volumes: ["s3:shared/one"] }, worker: { volumes: ["s3:shared/two"] }, }, } as ComposeSpecification; expect(() => getComposeS3Claims(compose)).toThrow( "S3 key shared is claimed for both one and two", ); }); test("does not render S3 declarations as filesystem volumes", () => { const service = { image: "example", volumes: ["s3:assets"] } as Service; expect(volumesToPvc("project", service)).toEqual([]); const deployment = serviceToDeployment( "project", "app", { services: { app: service } } as ComposeSpecification, service, process.cwd(), { AWS_ACCESS_KEY_ID: "test" }, ); expect(deployment.spec?.template.spec?.volumes).toBeUndefined(); expect(deployment.spec?.template.spec?.containers[0]?.envFrom).toEqual([ { secretRef: { name: "app-env" } }, ]); }); test("builds a Garage UI object URL for a bucket", () => { expect(getS3UiUrl("shared assets")).toBe( "http://garage-ui.garage-system.svc.cluster.local/buckets/shared%20assets/objects", ); }); });