feat(P06): docs & hardening — AGENTS.md/README fixes, agent tests, Gitea tests, multi-project tests, version 0.7.0
---ci--- phase: 6 milestone: v0.7.0 plan: 06 task: P06-all status: execute ---/ci---
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import { GiteaClient, generateReleaseNotes, GiteaReleaseConfig } from "../core/gitea.js";
|
||||
|
||||
const defaultConfig: GiteaReleaseConfig = {
|
||||
baseUrl: "https://git.example.com",
|
||||
token: "test-token-123",
|
||||
owner: "testorg",
|
||||
repo: "testrepo",
|
||||
};
|
||||
|
||||
function makeReleaseResponse(overrides: Partial<{
|
||||
id: number;
|
||||
tag_name: string;
|
||||
name: string;
|
||||
body: string;
|
||||
url: string;
|
||||
html_url: string;
|
||||
draft: boolean;
|
||||
prerelease: boolean;
|
||||
}> = {}): Record<string, unknown> {
|
||||
return {
|
||||
id: overrides.id ?? 1,
|
||||
tag_name: overrides.tag_name ?? "v1.0.0",
|
||||
name: overrides.name ?? "v1.0.0",
|
||||
body: overrides.body ?? "Release notes",
|
||||
url: overrides.url ?? "https://git.example.com/api/v1/repos/testorg/testrepo/releases/1",
|
||||
html_url: overrides.html_url ?? "https://git.example.com/testorg/testrepo/releases/tag/v1.0.0",
|
||||
draft: overrides.draft ?? false,
|
||||
prerelease: overrides.prerelease ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
describe("GiteaClient", () => {
|
||||
let originalFetch: typeof globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
originalFetch = globalThis.fetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
describe("createRelease", () => {
|
||||
it("creates a release via POST", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => makeReleaseResponse({ tag_name: "v1.0.0", name: "v1.0.0" }),
|
||||
});
|
||||
|
||||
const release = await client.createRelease({
|
||||
tag_name: "v1.0.0",
|
||||
name: "v1.0.0",
|
||||
body: "Initial release",
|
||||
});
|
||||
|
||||
expect(release.tag_name).toBe("v1.0.0");
|
||||
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
|
||||
const call = (globalThis.fetch as jest.Mock).mock.calls[0];
|
||||
expect(call[0]).toContain("/releases");
|
||||
expect(call[1].method).toBe("POST");
|
||||
expect(call[1].headers.Authorization).toBe("token test-token-123");
|
||||
});
|
||||
|
||||
it("throws on non-ok response", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 409,
|
||||
text: async () => "Conflict: tag already exists",
|
||||
});
|
||||
|
||||
await expect(client.createRelease({
|
||||
tag_name: "v1.0.0",
|
||||
name: "v1.0.0",
|
||||
body: "",
|
||||
})).rejects.toThrow("Gitea API error: 409");
|
||||
});
|
||||
});
|
||||
|
||||
describe("listReleases", () => {
|
||||
it("lists releases via GET", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => [
|
||||
makeReleaseResponse({ id: 1, tag_name: "v1.0.0" }),
|
||||
makeReleaseResponse({ id: 2, tag_name: "v1.1.0" }),
|
||||
],
|
||||
});
|
||||
|
||||
const releases = await client.listReleases();
|
||||
expect(releases).toHaveLength(2);
|
||||
expect(releases[0].tag_name).toBe("v1.0.0");
|
||||
expect(releases[1].tag_name).toBe("v1.1.0");
|
||||
});
|
||||
|
||||
it("throws on non-ok response", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(client.listReleases()).rejects.toThrow("Gitea API error: 500");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getReleaseByTag", () => {
|
||||
it("returns release when found", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => makeReleaseResponse({ tag_name: "v1.0.0" }),
|
||||
});
|
||||
|
||||
const release = await client.getReleaseByTag("v1.0.0");
|
||||
expect(release).not.toBeNull();
|
||||
expect(release!.tag_name).toBe("v1.0.0");
|
||||
});
|
||||
|
||||
it("returns null on 404", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const release = await client.getReleaseByTag("v0.0.0");
|
||||
expect(release).toBeNull();
|
||||
});
|
||||
|
||||
it("throws on other non-ok status", async () => {
|
||||
const client = new GiteaClient(defaultConfig);
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(client.getReleaseByTag("v1.0.0")).rejects.toThrow("Gitea API error: 500");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateReleaseNotes", () => {
|
||||
let dir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-gitea-test-"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("parses git log into categorized sections", () => {
|
||||
const gitDir = path.join(dir, "repo");
|
||||
fs.mkdirSync(gitDir, { recursive: true });
|
||||
|
||||
const { execSync } = require("node:child_process");
|
||||
execSync("git init", { cwd: gitDir, stdio: "pipe" });
|
||||
execSync('git config user.email "test@test.com"', { cwd: gitDir, stdio: "pipe" });
|
||||
execSync('git config user.name "Test"', { cwd: gitDir, stdio: "pipe" });
|
||||
|
||||
fs.writeFileSync(path.join(gitDir, "file1.txt"), "hello");
|
||||
execSync("git add -A", { cwd: gitDir, stdio: "pipe" });
|
||||
execSync('git commit -m "feat: add authentication"', { cwd: gitDir, stdio: "pipe" });
|
||||
|
||||
fs.writeFileSync(path.join(gitDir, "file2.txt"), "world");
|
||||
execSync("git add -A", { cwd: gitDir, stdio: "pipe" });
|
||||
execSync('git commit -m "fix: resolve login bug"', { cwd: gitDir, stdio: "pipe" });
|
||||
|
||||
execSync("git tag v1.0.0", { cwd: gitDir, stdio: "pipe" });
|
||||
|
||||
const notes = generateReleaseNotes(gitDir, null, "v1.0.0");
|
||||
expect(notes).toContain("New Features");
|
||||
expect(notes).toContain("add authentication");
|
||||
expect(notes).toContain("Bug Fixes");
|
||||
expect(notes).toContain("resolve login bug");
|
||||
});
|
||||
|
||||
it("returns no-commits message when no commits found", () => {
|
||||
const nonExistent = path.join(dir, "nonexistent");
|
||||
const notes = generateReleaseNotes(nonExistent, null, "v0.0.0");
|
||||
expect(notes).toContain("No commits found");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user