Initial curated projectbrain source from 8a7b1e0651dbdab97d744cbd2e653ab3970ac6c5
This commit is contained in:
commit
5aa3e8d685
700 files changed
+132801
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { loadEnv } from "../src/config/env.js";
|
||||
import { createPortfolioAnalysisJob } from "../src/repositories/analysisJobRepository.js";
|
||||
import { refreshPortfolioCatalog } from "../src/services/portfolioControlPlane.js";
|
||||
import { registerAnalysisJobRoutes } from "../src/routes/analysisJobs.js";
|
||||
import Fastify from "fastify";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
inventory: vi.fn(), links: vi.fn(), findJob: vi.fn(), findFirstJob: vi.fn(), createJob: vi.fn(),
|
||||
catalog: vi.fn(), upsert: vi.fn(), unavailable: vi.fn(),
|
||||
}));
|
||||
vi.mock("../src/integrations/gitea/service.js", () => ({
|
||||
getGiteaClient: () => ({ listRepositories: mocks.inventory }),
|
||||
listLinkedRepositories: mocks.links,
|
||||
}));
|
||||
vi.mock("../src/db/prisma.js", () => ({ prisma: {
|
||||
analysisJob: { findUnique: mocks.findJob, findFirst: mocks.findFirstJob, create: mocks.createJob },
|
||||
portfolioRepository: { findMany: mocks.catalog, upsert: mocks.upsert, updateMany: mocks.unavailable },
|
||||
} }));
|
||||
|
||||
const env = loadEnv({ NODE_ENV: "test", PROJECTBRAIN_DATA_SOURCE: "database",
|
||||
DATABASE_URL: "postgresql://fixture:fixture@localhost:5432/fixture",
|
||||
PROJECTBRAIN_OWNER_USERNAME: "fixture-owner", PROJECTBRAIN_OWNER_PASSWORD: "fixture-only-password",
|
||||
PROJECTBRAIN_AUTH_SECRET: "fixture-only-auth-secret-at-least-32-characters" });
|
||||
const repositories = (count: number) => Array.from({ length: count }, (_, index) => ({
|
||||
id: index + 1, name: `repo-${index}`, full_name: `Owner/repo-${index}`,
|
||||
owner: { login: "Owner" }, default_branch: "main", permissions: { pull: true },
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.links.mockResolvedValue([]);
|
||||
mocks.catalog.mockResolvedValue([]);
|
||||
mocks.findJob.mockResolvedValue(null);
|
||||
mocks.findFirstJob.mockResolvedValue(null);
|
||||
mocks.createJob.mockImplementation(async ({ data }) => ({
|
||||
id: "fixture-job", status: "queued", progressTotal: data.progressTotal, progressCurrent: 0,
|
||||
}));
|
||||
});
|
||||
|
||||
describe("bounded complete portfolio analysis", () => {
|
||||
it.each([58, 200, 201])("enforces the same %i-selection boundary at the project HTTP route", async (count) => {
|
||||
mocks.links.mockResolvedValue(repositories(count).map((repository) => ({
|
||||
id: String(repository.id), projectId: "fixture-project", projectSlug: "fixture-project",
|
||||
projectName: "Fixture", owner: "Owner", repository: repository.name,
|
||||
fullName: repository.full_name, defaultBranch: "main", accessMode: "read_only",
|
||||
})));
|
||||
const app = Fastify();
|
||||
app.decorateRequest("authUser", null);
|
||||
app.addHook("onRequest", async (request) => {
|
||||
request.authUser = { id: "fixture-owner", username: "fixture-owner", role: "owner", credentialVersion: 1 };
|
||||
});
|
||||
await registerAnalysisJobRoutes(app, { env });
|
||||
try {
|
||||
const response = await app.inject({ method: "POST", url: "/api/projects/fixture-project/analysis-jobs",
|
||||
payload: { repositoryIds: Array.from({ length: count }, (_, index) => String(index + 1)) } });
|
||||
if (count <= 200) {
|
||||
expect(response.statusCode).toBe(202);
|
||||
expect(response.json().data.job.progressTotal).toBe(count);
|
||||
expect(mocks.createJob.mock.calls[0][0].data.items.create).toHaveLength(count);
|
||||
} else {
|
||||
expect(response.statusCode).toBeGreaterThanOrEqual(400);
|
||||
expect(mocks.createJob).not.toHaveBeenCalled();
|
||||
}
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
it.each([51, 58, 200])("persists all %i targets without truncation", async (count) => {
|
||||
mocks.inventory.mockResolvedValue(repositories(count));
|
||||
const result = await createPortfolioAnalysisJob(env, "fixture-actor", {});
|
||||
expect(result.job.progressTotal).toBe(count);
|
||||
const data = mocks.createJob.mock.calls[0][0].data;
|
||||
expect(data.items.create).toHaveLength(count);
|
||||
expect(data.requestJson.repositoryIds).toHaveLength(count);
|
||||
expect(new Set(data.items.create.map((item: { sequence: number }) => item.sequence)).size).toBe(count);
|
||||
});
|
||||
|
||||
it("rejects 201 targets before job persistence", async () => {
|
||||
mocks.inventory.mockResolvedValue(repositories(201));
|
||||
await expect(createPortfolioAnalysisJob(env, "fixture-actor", {})).rejects.toThrow("ANALYSIS_REPOSITORY_LIMIT_EXCEEDED");
|
||||
expect(mocks.createJob).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not persist a job or mark cached repositories unavailable after incomplete inventory", async () => {
|
||||
mocks.inventory.mockRejectedValue(new Error("GITEA_INVENTORY_LIMIT_EXCEEDED"));
|
||||
await expect(createPortfolioAnalysisJob(env, "fixture-actor", {})).rejects.toThrow("GITEA_INVENTORY_LIMIT_EXCEEDED");
|
||||
await expect(refreshPortfolioCatalog(env)).rejects.toThrow("GITEA_INVENTORY_LIMIT_EXCEEDED");
|
||||
expect(mocks.createJob).not.toHaveBeenCalled();
|
||||
expect(mocks.upsert).not.toHaveBeenCalled();
|
||||
expect(mocks.unavailable).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user