Add store persist/hydrate regressions for terminal branch associations and a server test that a complete empty open list does not query closed PRs. Inline the open-only matcher state so the next repo target can win. Co-authored-by: serkraser <serkraser@gmail.com>
106 lines
3.0 KiB
JavaScript
106 lines
3.0 KiB
JavaScript
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
|
|
const listMock = mock(async () => ({ data: [] }));
|
|
|
|
mock.module('../git/index.js', () => ({
|
|
getRemotes: async () => [],
|
|
getStatus: async () => null,
|
|
}));
|
|
|
|
mock.module('./repo/index.js', () => ({
|
|
resolveGitHubRepoFromDirectory: async () => null,
|
|
}));
|
|
|
|
mock.module('./rate-limit.js', () => ({
|
|
noteIfGitHubRateLimit: () => {},
|
|
}));
|
|
|
|
const { findFirstMatchingPr, invalidateRepoPullsCache } = await import('./pr-status.js');
|
|
|
|
const openPr = {
|
|
number: 15,
|
|
state: 'open',
|
|
head: {
|
|
ref: 'feature',
|
|
label: 'acme:feature',
|
|
user: { login: 'acme' },
|
|
repo: { owner: { login: 'acme' }, name: 'app' },
|
|
},
|
|
};
|
|
|
|
const closedPr = {
|
|
number: 12,
|
|
state: 'closed',
|
|
merged_at: '2026-01-01T00:00:00Z',
|
|
head: {
|
|
ref: 'feature',
|
|
label: 'acme:feature',
|
|
user: { login: 'acme' },
|
|
repo: { owner: { login: 'acme' }, name: 'app' },
|
|
},
|
|
};
|
|
|
|
describe('findFirstMatchingPr open-only branch status', () => {
|
|
beforeEach(() => {
|
|
listMock.mockReset();
|
|
invalidateRepoPullsCache('acme', 'app');
|
|
});
|
|
|
|
test('returns a matching open PR', async () => {
|
|
listMock.mockImplementation(async ({ state }) => {
|
|
if (state === 'open') {
|
|
return { data: [openPr] };
|
|
}
|
|
return { data: [closedPr] };
|
|
});
|
|
|
|
const pr = await findFirstMatchingPr({
|
|
octokit: { rest: { pulls: { list: listMock } } },
|
|
target: { repo: { owner: 'acme', repo: 'app' }, remoteName: 'origin' },
|
|
branch: 'feature',
|
|
sourceCandidates: [{ repo: { owner: 'acme', repo: 'app' } }],
|
|
force: true,
|
|
});
|
|
|
|
expect(pr?.number).toBe(15);
|
|
expect(listMock.mock.calls.every((call) => call[0]?.state === 'open')).toBe(true);
|
|
});
|
|
|
|
test('returns null when only a closed/merged PR exists for the head branch', async () => {
|
|
listMock.mockImplementation(async ({ state }) => {
|
|
if (state === 'open') {
|
|
return { data: [] };
|
|
}
|
|
return { data: [closedPr] };
|
|
});
|
|
|
|
const pr = await findFirstMatchingPr({
|
|
octokit: { rest: { pulls: { list: listMock } } },
|
|
target: { repo: { owner: 'acme', repo: 'app' }, remoteName: 'origin' },
|
|
branch: 'feature',
|
|
sourceCandidates: [{ repo: { owner: 'acme', repo: 'app' } }],
|
|
force: true,
|
|
});
|
|
|
|
expect(pr).toBeNull();
|
|
expect(listMock.mock.calls.every((call) => call[0]?.state === 'open')).toBe(true);
|
|
expect(listMock.mock.calls.some((call) => call[0]?.state === 'closed')).toBe(false);
|
|
});
|
|
|
|
test('does not query closed PRs when the open list is complete and empty', async () => {
|
|
listMock.mockImplementation(async () => ({ data: [] }));
|
|
|
|
const pr = await findFirstMatchingPr({
|
|
octokit: { rest: { pulls: { list: listMock } } },
|
|
target: { repo: { owner: 'acme', repo: 'app' }, remoteName: 'origin' },
|
|
branch: 'feature',
|
|
sourceCandidates: [{ repo: { owner: 'acme', repo: 'app' } }],
|
|
force: true,
|
|
});
|
|
|
|
expect(pr).toBeNull();
|
|
expect(listMock.mock.calls).toHaveLength(1);
|
|
expect(listMock.mock.calls[0]?.[0]?.state).toBe('open');
|
|
});
|
|
});
|