UNPKG

valaxy-addon-git-log

Version:

Integrates git logs into your page of Valaxy site.

247 lines (196 loc) 7.85 kB
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { getGitHubToken, guessGitHubUsername, parseGithubUrl, resolveGitHubUsers } from '../../utils/github' describe('parseGithubUrl', () => { it('should parse HTTPS GitHub URL', () => { const result = parseGithubUrl('https://github.com/valaxyjs/valaxy-addon-git-log') expect(result).toEqual({ owner: 'valaxyjs', repo: 'valaxy-addon-git-log' }) }) it('should parse HTTPS GitHub URL with .git suffix', () => { const result = parseGithubUrl('https://github.com/valaxyjs/valaxy-addon-git-log.git') expect(result).toEqual({ owner: 'valaxyjs', repo: 'valaxy-addon-git-log' }) }) it('should parse SSH GitHub URL', () => { const result = parseGithubUrl('git@github.com:valaxyjs/valaxy-addon-git-log.git') expect(result).toEqual({ owner: 'valaxyjs', repo: 'valaxy-addon-git-log' }) }) it('should parse SSH GitHub URL without .git suffix', () => { const result = parseGithubUrl('git@github.com:user/repo') expect(result).toEqual({ owner: 'user', repo: 'repo' }) }) it('should throw for invalid URL', () => { expect(() => parseGithubUrl('https://gitlab.com/user/repo')) .toThrow('valaxy-addon-git-log: Invalid GitHub URL') }) it('should throw for empty string', () => { expect(() => parseGithubUrl('')) .toThrow('valaxy-addon-git-log: Invalid GitHub URL') }) }) describe('guessGitHubUsername', () => { it('should extract username from noreply email', () => { expect(guessGitHubUsername('yunyou@users.noreply.github.com')).toBe('yunyou') }) it('should extract username from noreply email with numeric prefix', () => { expect(guessGitHubUsername('12345+yunyou@users.noreply.github.com')).toBe('yunyou') }) it('should return null for regular email', () => { expect(guessGitHubUsername('user@example.com')).toBeNull() }) it('should return null for empty string', () => { expect(guessGitHubUsername('')).toBeNull() }) }) describe('getGitHubToken', () => { const original = { GITHUB_TOKEN: process.env.GITHUB_TOKEN, GH_TOKEN: process.env.GH_TOKEN } afterEach(() => { process.env.GITHUB_TOKEN = original.GITHUB_TOKEN process.env.GH_TOKEN = original.GH_TOKEN }) it('should prefer GITHUB_TOKEN', () => { process.env.GITHUB_TOKEN = 'gh_token' process.env.GH_TOKEN = 'cli_token' expect(getGitHubToken()).toBe('gh_token') }) it('should fall back to GH_TOKEN', () => { delete process.env.GITHUB_TOKEN process.env.GH_TOKEN = 'cli_token' expect(getGitHubToken()).toBe('cli_token') }) it('should return undefined when no token is set', () => { delete process.env.GITHUB_TOKEN delete process.env.GH_TOKEN expect(getGitHubToken()).toBeUndefined() }) }) describe('resolveGitHubUsers', () => { const original = { GITHUB_TOKEN: process.env.GITHUB_TOKEN, GH_TOKEN: process.env.GH_TOKEN } beforeEach(() => { delete process.env.GITHUB_TOKEN delete process.env.GH_TOKEN }) afterEach(() => { process.env.GITHUB_TOKEN = original.GITHUB_TOKEN process.env.GH_TOKEN = original.GH_TOKEN }) it('should return empty map for empty emails', async () => { const result = await resolveGitHubUsers('owner', 'repo', []) expect(result).toBeInstanceOf(Map) expect(result.size).toBe(0) }) it('should authenticate Octokit when a token is present', async () => { process.env.GITHUB_TOKEN = 'secret_token' const octokitSpy = vi.fn() vi.doMock('@octokit/rest', () => ({ Octokit: class { constructor(opts?: unknown) { octokitSpy(opts) } repos = { listCommits: vi.fn().mockResolvedValue({ data: [] }), } }, })) const { resolveGitHubUsers: resolve } = await import('../../utils/github') await resolve('owner', 'repo', ['user@example.com']) expect(octokitSpy).toHaveBeenCalledWith({ auth: 'secret_token' }) vi.doUnmock('@octokit/rest') }) it('should construct Octokit without auth when no token is present', async () => { const octokitSpy = vi.fn() vi.doMock('@octokit/rest', () => ({ Octokit: class { constructor(opts?: unknown) { octokitSpy(opts) } repos = { listCommits: vi.fn().mockResolvedValue({ data: [] }), } }, })) const { resolveGitHubUsers: resolve } = await import('../../utils/github') await resolve('owner', 'repo', ['user@example.com']) expect(octokitSpy).toHaveBeenCalledWith({}) vi.doUnmock('@octokit/rest') }) it('should resolve emails from recent commits', async () => { vi.doMock('@octokit/rest', () => ({ Octokit: class { repos = { listCommits: vi.fn().mockResolvedValue({ data: [ { commit: { author: { email: 'alice@example.com' } }, author: { login: 'alice' } }, { commit: { author: { email: 'bob@example.com' } }, author: { login: 'bob' } }, ], }), } }, })) // Re-import to pick up the mock const { resolveGitHubUsers: resolve } = await import('../../utils/github') const result = await resolve('owner', 'repo', ['alice@example.com', 'bob@example.com']) expect(result.get('alice@example.com')).toBe('alice') expect(result.get('bob@example.com')).toBe('bob') vi.doUnmock('@octokit/rest') }) it('should fall back to per-email queries for unresolved emails', async () => { const listCommitsFn = vi.fn() // Phase 1: recent commits - returns only alice .mockResolvedValueOnce({ data: [ { commit: { author: { email: 'alice@example.com' } }, author: { login: 'alice' } }, ], }) // Phase 3: per-email query for bob .mockResolvedValueOnce({ data: [ { commit: { author: { email: 'bob@example.com' } }, author: { login: 'bob' } }, ], }) vi.doMock('@octokit/rest', () => ({ Octokit: class { repos = { listCommits: listCommitsFn } }, })) const { resolveGitHubUsers: resolve } = await import('../../utils/github') const result = await resolve('owner', 'repo', ['alice@example.com', 'bob@example.com']) expect(result.get('alice@example.com')).toBe('alice') expect(result.get('bob@example.com')).toBe('bob') // Phase 1 (1) + Phase 3 per-email (1) = 2 calls expect(listCommitsFn).toHaveBeenCalledTimes(2) vi.doUnmock('@octokit/rest') }) it('should gracefully handle API errors', async () => { vi.doMock('@octokit/rest', () => ({ Octokit: class { repos = { listCommits: vi.fn().mockRejectedValue(new Error('Network error')), } }, })) const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const { resolveGitHubUsers: resolve } = await import('../../utils/github') const result = await resolve('owner', 'repo', ['test@example.com']) expect(result.size).toBe(0) expect(warnSpy).toHaveBeenCalledOnce() warnSpy.mockRestore() vi.doUnmock('@octokit/rest') }) it('should deduplicate emails', async () => { const listCommitsFn = vi.fn().mockResolvedValue({ data: [ { commit: { author: { email: 'dup@example.com' } }, author: { login: 'dupuser' } }, ], }) vi.doMock('@octokit/rest', () => ({ Octokit: class { repos = { listCommits: listCommitsFn } }, })) const { resolveGitHubUsers: resolve } = await import('../../utils/github') const result = await resolve('owner', 'repo', ['dup@example.com', 'dup@example.com', 'dup@example.com']) expect(result.get('dup@example.com')).toBe('dupuser') // Only 1 API call despite 3 duplicate emails expect(listCommitsFn).toHaveBeenCalledTimes(1) vi.doUnmock('@octokit/rest') }) })