Files
altdock/tests/api.test.mjs
T
amania-jailbreak 93825ebc64 初回コミット: AltStore PAL向けADPホスティングSaaS
- Fastify API・Node Worker・PostgreSQL・S3互換ストレージ構成
- ADP ZIPのマルチパートアップロードとManifest検証(パストラバーサル・ZIP爆弾等を拒否)
- manifest.json/signatureは再シリアライズせず元バイト列を保持
- Cloudflare風の運用向け管理ダッシュボード(shadcn/Radix・日本語UI・4ルート)
  概要/Sources/アプリ/リリース + ルートランディングページ
- 汎用OIDC SSO・Workspace単位の認可・demo mode
- 匿名配布: /sources/{slug}/source.json, /artifacts/{releaseId}/manifest.json
- Docker Compose対応
2026-08-05 10:09:58 +09:00

93 lines
5.1 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import test from "node:test";
process.env.AUTH_MODE = "demo";
process.env.STORAGE_MODE = "local";
process.env.PUBLIC_BASE_URL = "http://localhost:4000";
process.env.WEB_ORIGIN = "http://localhost:3000";
process.env.LOCAL_STORAGE_DIR = ".data/test-storage";
process.env.PROCESS_INLINE = "true";
delete process.env.DATABASE_URL;
const { createServer } = await import("../apps/api/src/server.ts");
const runtime = await createServer();
const app = runtime.app;
test.after(async () => {
await app.close();
});
test("creates a source and returns a dashboard in demo mode", async () => {
const sourceResponse = await app.inject({
method: "POST",
url: "/api/v1/sources",
headers: { "x-demo-user": "test@example.com", "content-type": "application/json" },
payload: { name: "Test Source", subtitle: "Test apps", visibility: "draft" },
});
assert.equal(sourceResponse.statusCode, 201);
const source = sourceResponse.json().source;
assert.equal(source.visibility, "draft");
const dashboardResponse = await app.inject({ method: "GET", url: "/api/v1/dashboard", headers: { "x-demo-user": "test@example.com" } });
assert.equal(dashboardResponse.statusCode, 200);
const dashboard = dashboardResponse.json();
assert.ok(dashboard.sources.some((entry) => entry.source.id === source.id));
});
test("does not expose draft sources publicly", async () => {
const response = await app.inject({ method: "GET", url: "/sources/test-source/source.json" });
assert.equal(response.statusCode, 404);
});
test("validates, stores, publishes, and serves an ADP package", async () => {
const root = await mkdtemp(join(tmpdir(), "altdock-test-adp-"));
const fixtureRoot = join(root, "fixture");
await (await import("node:fs/promises")).mkdir(join(fixtureRoot, "variant"), { recursive: true });
const manifest = Buffer.from(JSON.stringify({
distributionPackageRevision: 1,
appleItemId: "123456789",
bundleId: "com.example.fixture",
shortVersionString: "1.0",
bundleVersion: "1",
platforms: ["ios"],
minimumSystemVersions: { ios: "17.4" },
variants: [{ assetPath: "variant/fixture.ipa", installTargets: [] }],
deltas: [],
}));
const signature = Buffer.from("signed-fixture");
const ipa = Buffer.from("not-an-installable-ipa-fixture");
await writeFile(join(fixtureRoot, "manifest.json"), manifest);
await writeFile(join(fixtureRoot, "signature"), signature);
await writeFile(join(fixtureRoot, "variant/fixture.ipa"), ipa);
const archivePath = join(root, "fixture.zip");
await promisify(execFile)("zip", ["-q", "-r", archivePath, "manifest.json", "signature", "variant"], { cwd: fixtureRoot });
const archive = await readFile(archivePath);
const sourceResponse = await app.inject({ method: "POST", url: "/api/v1/sources", headers: { "x-demo-user": "fixture@example.com", "content-type": "application/json" }, payload: { name: "Fixture Source", visibility: "public" } });
const source = sourceResponse.json().source;
const uploadResponse = await app.inject({ method: "POST", url: "/api/v1/uploads", headers: { "x-demo-user": "fixture@example.com", "content-type": "application/json" }, payload: { sourceId: source.id, filename: "fixture.zip", sizeBytes: archive.length } });
assert.equal(uploadResponse.statusCode, 201);
const upload = uploadResponse.json();
const putResponse = await app.inject({ method: "PUT", url: new URL(upload.uploadPlan.uploadUrl).pathname, headers: { "content-type": "application/zip" }, payload: archive });
assert.equal(putResponse.statusCode, 200, putResponse.body);
const completeResponse = await app.inject({ method: "POST", url: `/api/v1/uploads/${upload.upload.id}/complete`, headers: { "x-demo-user": "fixture@example.com", "content-type": "application/json" }, payload: {} });
assert.equal(completeResponse.statusCode, 202, completeResponse.body);
const release = completeResponse.json().release;
assert.equal(release.status, "ready");
const publishResponse = await app.inject({ method: "POST", url: `/api/v1/releases/${release.id}/publish`, headers: { "x-demo-user": "fixture@example.com" } });
assert.equal(publishResponse.statusCode, 200, publishResponse.body);
const sourceJsonResponse = await app.inject({ method: "GET", url: `/sources/${source.slug}/source.json` });
assert.equal(sourceJsonResponse.statusCode, 200, sourceJsonResponse.body);
const sourceJson = sourceJsonResponse.json();
assert.equal(sourceJson.apps[0].bundleIdentifier, "com.example.fixture");
assert.match(sourceJson.apps[0].versions[0].downloadURL, new RegExp(`/artifacts/${release.id}/manifest\\.json$`));
const artifactResponse = await app.inject({ method: "GET", url: `/artifacts/${release.id}/manifest.json` });
assert.equal(artifactResponse.statusCode, 200, artifactResponse.body);
assert.deepEqual(artifactResponse.rawPayload, manifest);
await rm(root, { recursive: true, force: true });
});