Per provider (github|gitlab|gitea) a project override stored in ~/.config/openchamber/projects/<projectId>.json under gitProviders wins over the global settings.json value (precedence: project override > global > built-in default). Server forge routes resolve the override per request directory (worktree-aware via git-common-dir + containment + path fallback, 60s TTL cache); the override host is also accepted for remote parsing and client detection. New GET/PUT /api/projects/:projectId/git-providers route; client openchamberConfig preserves the server-owned gitProviders key; Projects page gains a Git provider API base URLs section; detection store hydrates per-project overrides (memory-only, server-authoritative).
84 lines
3.0 KiB
JavaScript
84 lines
3.0 KiB
JavaScript
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
// getOctokitOrNull reads auth + config modules; mock them all so the base URL
|
|
// resolution can be asserted without a real token, data dir, or Octokit client.
|
|
const mockState = vi.hoisted(() => ({
|
|
octokitConfigs: [],
|
|
getGitHubAuth: vi.fn(),
|
|
isGhCliActive: vi.fn(),
|
|
isGhCliDisabled: vi.fn(),
|
|
getGhCliToken: vi.fn(),
|
|
getProviderApiBaseUrl: vi.fn(),
|
|
getEffectiveProviderApiBaseUrl: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@octokit/rest', () => ({
|
|
Octokit: class {
|
|
constructor(config) {
|
|
mockState.octokitConfigs.push(config);
|
|
}
|
|
},
|
|
}));
|
|
|
|
vi.mock('./auth.js', () => ({
|
|
getGitHubAuth: mockState.getGitHubAuth,
|
|
isGhCliActive: mockState.isGhCliActive,
|
|
isGhCliDisabled: mockState.isGhCliDisabled,
|
|
}));
|
|
|
|
vi.mock('./gh-cli-credential.js', () => ({
|
|
getGhCliToken: mockState.getGhCliToken,
|
|
}));
|
|
|
|
vi.mock('../git-providers/config.js', () => ({
|
|
getProviderApiBaseUrl: mockState.getProviderApiBaseUrl,
|
|
}));
|
|
|
|
vi.mock('../git-providers/project-config.js', () => ({
|
|
getEffectiveProviderApiBaseUrl: mockState.getEffectiveProviderApiBaseUrl,
|
|
}));
|
|
|
|
const { getOctokitOrNull } = await import('./octokit.js');
|
|
|
|
beforeEach(() => {
|
|
mockState.octokitConfigs.length = 0;
|
|
mockState.getGitHubAuth.mockReset();
|
|
mockState.isGhCliActive.mockReset().mockReturnValue(false);
|
|
mockState.isGhCliDisabled.mockReset().mockReturnValue(false);
|
|
mockState.getGhCliToken.mockReset().mockReturnValue(null);
|
|
mockState.getProviderApiBaseUrl.mockReset();
|
|
mockState.getEffectiveProviderApiBaseUrl.mockReset();
|
|
});
|
|
|
|
describe('getOctokitOrNull base URL resolution', () => {
|
|
test('uses the global base URL without a directory and never consults project overrides', () => {
|
|
mockState.getGitHubAuth.mockReturnValue({ accessToken: 'ghp-test' });
|
|
mockState.getProviderApiBaseUrl.mockReturnValue('https://api.github.com');
|
|
|
|
const octokit = getOctokitOrNull();
|
|
|
|
expect(octokit).not.toBeNull();
|
|
expect(mockState.octokitConfigs).toHaveLength(1);
|
|
expect(mockState.octokitConfigs[0].auth).toBe('ghp-test');
|
|
expect(mockState.octokitConfigs[0].baseUrl).toBe('https://api.github.com');
|
|
expect(mockState.getEffectiveProviderApiBaseUrl).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('resolves the per-project override base URL for a directory', () => {
|
|
mockState.getGitHubAuth.mockReturnValue({ accessToken: 'ghp-test' });
|
|
mockState.getEffectiveProviderApiBaseUrl.mockReturnValue('https://github.enterprise.example');
|
|
|
|
const octokit = getOctokitOrNull('/work/override-project');
|
|
|
|
expect(octokit).not.toBeNull();
|
|
expect(mockState.getEffectiveProviderApiBaseUrl).toHaveBeenCalledWith('github', '/work/override-project');
|
|
expect(mockState.octokitConfigs[0].baseUrl).toBe('https://github.enterprise.example');
|
|
});
|
|
|
|
test('returns null without a token', () => {
|
|
mockState.getGitHubAuth.mockReturnValue(null);
|
|
|
|
expect(getOctokitOrNull('/work/override-project')).toBeNull();
|
|
expect(mockState.octokitConfigs).toHaveLength(0);
|
|
});
|
|
}); |