Files
donutbrowser/donut-sync/test/presign-endpoint.e2e-spec.ts
T
2026-08-10 15:47:31 +04:00

202 lines
6.5 KiB
TypeScript

import { INestApplication } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { Test, TestingModule } from "@nestjs/testing";
import request from "supertest";
import { App } from "supertest/types";
import { AppController } from "./../src/app.controller.js";
import { AppService } from "./../src/app.service.js";
import { SyncModule } from "./../src/sync/sync.module.js";
import {
configureTestEnv,
TEST_S3_ENDPOINT,
TEST_SYNC_TOKEN,
waitForTestS3,
} from "./test-env.js";
// Presigning is offline, so this host never has to accept a connection — the
// assertions are about which host ends up in the signed URL.
const PUBLIC_ENDPOINT = "https://storage.example.com";
// Only needs to be present for the server to consider itself cloud-mode; no
// token is verified against it in these assertions.
const CLOUD_PUBLIC_KEY =
"-----BEGIN PUBLIC KEY-----\nnot-a-real-key\n-----END PUBLIC KEY-----";
interface PresignResponse {
url: string;
}
interface PresignBatchResponse {
items: Array<{ key: string; url: string }>;
}
interface ReadyResponse {
status: string;
s3: boolean;
storageEndpoint: string;
}
async function bootstrap(publicEndpoint: string | undefined) {
configureTestEnv();
if (publicEndpoint) {
process.env.S3_PUBLIC_ENDPOINT = publicEndpoint;
} else {
delete process.env.S3_PUBLIC_ENDPOINT;
}
await waitForTestS3();
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot({ isGlobal: true }), SyncModule],
controllers: [AppController],
providers: [AppService],
}).compile();
const app = moduleFixture.createNestApplication<INestApplication<App>>();
await app.listen(0);
return app;
}
// A self-hosted server usually reaches its storage over a private address the
// desktop client has no route to. Signing client URLs against that address
// handed every client a URL it could not open, so uploads failed at connect
// while /health and /readyz stayed green.
describe("presigned URL host", () => {
describe("with S3_PUBLIC_ENDPOINT set", () => {
let app: INestApplication<App>;
beforeAll(async () => {
app = await bootstrap(PUBLIC_ENDPOINT);
});
afterAll(async () => {
delete process.env.S3_PUBLIC_ENDPOINT;
await app.close();
});
it("signs single upload URLs against the public endpoint", async () => {
const response = await request(app.getHttpServer())
.post("/v1/objects/presign-upload")
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
.send({ key: "endpoint/single.txt" })
.expect(200);
const { url } = response.body as PresignResponse;
expect(url.startsWith(PUBLIC_ENDPOINT)).toBe(true);
expect(url).not.toContain(TEST_S3_ENDPOINT);
});
it("signs batch upload URLs against the public endpoint", async () => {
const response = await request(app.getHttpServer())
.post("/v1/objects/presign-upload-batch")
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
.send({ items: [{ key: "endpoint/a.txt" }, { key: "endpoint/b.txt" }] })
.expect(200);
const { items } = response.body as PresignBatchResponse;
expect(items).toHaveLength(2);
for (const item of items) {
expect(item.url.startsWith(PUBLIC_ENDPOINT)).toBe(true);
}
});
it("signs download URLs against the public endpoint", async () => {
const response = await request(app.getHttpServer())
.post("/v1/objects/presign-download")
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
.send({ key: "endpoint/single.txt" })
.expect(200);
const { url } = response.body as PresignResponse;
expect(url.startsWith(PUBLIC_ENDPOINT)).toBe(true);
});
// The server's own S3 calls must keep using the private endpoint, or
// pointing clients at a public address would break the server itself.
it("still reaches storage over the private endpoint", async () => {
const response = await request(app.getHttpServer())
.post("/v1/objects/stat")
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
.send({ key: "endpoint/does-not-exist" })
.expect(200);
expect(response.body).toEqual({ exists: false });
});
it("reports the client-facing endpoint from /readyz", async () => {
const response = await request(app.getHttpServer())
.get("/readyz")
.expect(200);
const body = response.body as ReadyResponse;
expect(body.s3).toBe(true);
expect(body.storageEndpoint).toBe(PUBLIC_ENDPOINT);
});
});
// /readyz has no auth, so a managed deployment must not publish its storage
// host to anyone who can reach the probe.
describe("in cloud mode", () => {
let app: INestApplication<App>;
const previousKey = process.env.SYNC_JWT_PUBLIC_KEY;
beforeAll(async () => {
process.env.SYNC_JWT_PUBLIC_KEY = CLOUD_PUBLIC_KEY;
app = await bootstrap(PUBLIC_ENDPOINT);
});
afterAll(async () => {
if (previousKey === undefined) {
delete process.env.SYNC_JWT_PUBLIC_KEY;
} else {
process.env.SYNC_JWT_PUBLIC_KEY = previousKey;
}
delete process.env.S3_PUBLIC_ENDPOINT;
await app.close();
});
it("withholds the storage endpoint from /readyz", async () => {
const response = await request(app.getHttpServer())
.get("/readyz")
.expect(200);
const body = response.body as ReadyResponse;
expect(body.s3).toBe(true);
expect(body.storageEndpoint).toBeUndefined();
expect(JSON.stringify(body)).not.toContain("storage.example.com");
});
});
describe("without S3_PUBLIC_ENDPOINT", () => {
let app: INestApplication<App>;
beforeAll(async () => {
app = await bootstrap(undefined);
});
afterAll(async () => {
await app.close();
});
it("falls back to S3_ENDPOINT", async () => {
const response = await request(app.getHttpServer())
.post("/v1/objects/presign-upload")
.set("Authorization", `Bearer ${TEST_SYNC_TOKEN}`)
.send({ key: "endpoint/fallback.txt" })
.expect(200);
const { url } = response.body as PresignResponse;
expect(url.startsWith(TEST_S3_ENDPOINT)).toBe(true);
});
it("reports the fallback endpoint from /readyz", async () => {
const response = await request(app.getHttpServer())
.get("/readyz")
.expect(200);
expect((response.body as ReadyResponse).storageEndpoint).toBe(
TEST_S3_ENDPOINT,
);
});
});
});