UNPKG

await-github-file-change-cli

Version:

CLI tool to monitor GitHub file changes by polling for etag changes

196 lines (160 loc) 5.77 kB
import { describe, it, mock } from 'node:test'; import assert from 'node:assert'; import { parseGitHubUrl, getEtag, waitForEtagChange } from './index.js'; describe('parseGitHubUrl', () => { it('should parse a valid GitHub URL', () => { const url = 'https://github.com/gr2m/sandbox/blob/main/test-file'; const result = parseGitHubUrl(url); assert.strictEqual(result.owner, 'gr2m'); assert.strictEqual(result.repo, 'sandbox'); assert.strictEqual(result.ref, 'main'); assert.strictEqual(result.path, 'test-file'); }); it('should parse a GitHub URL with nested path', () => { const url = 'https://github.com/owner/repo/blob/main/path/to/file.txt'; const result = parseGitHubUrl(url); assert.strictEqual(result.owner, 'owner'); assert.strictEqual(result.repo, 'repo'); assert.strictEqual(result.ref, 'main'); assert.strictEqual(result.path, 'path/to/file.txt'); }); it('should parse a GitHub URL with different branch name', () => { const url = 'https://github.com/owner/repo/blob/develop/file.js'; const result = parseGitHubUrl(url); assert.strictEqual(result.owner, 'owner'); assert.strictEqual(result.repo, 'repo'); assert.strictEqual(result.ref, 'develop'); assert.strictEqual(result.path, 'file.js'); }); it('should parse a GitHub URL with commit SHA', () => { const url = 'https://github.com/owner/repo/blob/abc123def456/file.js'; const result = parseGitHubUrl(url); assert.strictEqual(result.owner, 'owner'); assert.strictEqual(result.repo, 'repo'); assert.strictEqual(result.ref, 'abc123def456'); assert.strictEqual(result.path, 'file.js'); }); it('should throw error for invalid URL', () => { const url = 'https://example.com/invalid/url'; assert.throws(() => { parseGitHubUrl(url); }, { message: 'Invalid GitHub URL. Expected format: https://github.com/owner/repo/blob/branch/path/to/file' }); }); it('should throw error for GitHub URL without blob path', () => { const url = 'https://github.com/owner/repo'; assert.throws(() => { parseGitHubUrl(url); }, { message: 'Invalid GitHub URL. Expected format: https://github.com/owner/repo/blob/branch/path/to/file' }); }); }); describe('getEtag', () => { it('should return etag from response headers', async () => { const mockOctokit = { request: mock.fn(async () => ({ headers: { etag: 'W/"abc123"' } })) }; const etag = await getEtag(mockOctokit, 'owner', 'repo', 'path/to/file', 'main'); assert.strictEqual(etag, '"abc123"'); assert.strictEqual(mockOctokit.request.mock.callCount(), 1); const callArgs = mockOctokit.request.mock.calls[0].arguments; assert.strictEqual(callArgs[0], 'HEAD /repos/{owner}/{repo}/contents/{path}'); assert.deepStrictEqual(callArgs[1], { owner: 'owner', repo: 'repo', path: 'path/to/file', ref: 'main', request: { method: 'HEAD' } }); }); it('should handle different etag formats', async () => { const mockOctokit = { request: mock.fn(async () => ({ headers: { etag: '"xyz789"' } })) }; const etag = await getEtag(mockOctokit, 'owner', 'repo', 'file.txt', 'develop'); assert.strictEqual(etag, '"xyz789"'); }); it('should pass commit SHA as ref', async () => { const mockOctokit = { request: mock.fn(async () => ({ headers: { etag: 'W/"commit-etag"' } })) }; const etag = await getEtag(mockOctokit, 'owner', 'repo', 'file.txt', 'abc123def456'); assert.strictEqual(etag, '"commit-etag"'); const callArgs = mockOctokit.request.mock.calls[0].arguments; assert.strictEqual(callArgs[1].ref, 'abc123def456'); }); }); describe('waitForEtagChange', () => { it.only('should resolve when etag changes', async (t) => { let callCount = 0; const mockOctokit = { request: mock.fn(async () => { callCount++; return { headers: { etag: callCount > 2 ? 'W/"new-etag"' : 'W/"old-etag"' } }; }) }; const promise = waitForEtagChange(mockOctokit, 'owner', 'repo', 'path', 'main', '"old-etag"'); const newEtag = await promise; assert.strictEqual(newEtag, '"new-etag"'); assert.ok(mockOctokit.request.mock.callCount() >= 3); }); it('should reject when request fails', async (t) => { const mockOctokit = { request: mock.fn(async () => { throw new Error('API Error'); }) }; await assert.rejects( async () => { await waitForEtagChange(mockOctokit, 'owner', 'repo', 'path', 'main', 'W/"etag"'); }, { message: 'API Error' } ); }); it('should immediately resolve if etag is already different', async (t) => { const mockOctokit = { request: mock.fn(async () => ({ headers: { etag: 'W/"different-etag"' } })) }; const newEtag = await waitForEtagChange(mockOctokit, 'owner', 'repo', 'path', 'develop', 'W/"old-etag"'); assert.strictEqual(newEtag, '"different-etag"'); assert.strictEqual(mockOctokit.request.mock.callCount(), 1); }); it('should pass ref to getEtag calls', async (t) => { const mockOctokit = { request: mock.fn(async () => ({ headers: { etag: 'W/"different-etag"' } })) }; await waitForEtagChange(mockOctokit, 'owner', 'repo', 'path', 'abc123def456', 'W/"old-etag"'); const callArgs = mockOctokit.request.mock.calls[0].arguments; assert.strictEqual(callArgs[1].ref, 'abc123def456'); }); });