UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

323 lines (259 loc) 9.81 kB
'use strict' const { describe, it, beforeEach, afterEach } = require('node:test') const assert = require('node:assert') const fs = require('node:fs') const { MockAgent, setGlobalDispatcher, getGlobalDispatcher } = require('undici') function loadValidateEcosystemLinksModule () { const modulePath = require.resolve('../../scripts/validate-ecosystem-links') delete require.cache[modulePath] return require(modulePath) } describe('extractGitHubLinks', () => { const { extractGitHubLinks } = loadValidateEcosystemLinksModule() it('extracts simple GitHub repository links', () => { const content = ` # Ecosystem - [fastify-helmet](https://github.com/fastify/fastify-helmet) - Important security headers for Fastify - [fastify-cors](https://github.com/fastify/fastify-cors) - CORS support ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 2) assert.deepStrictEqual(links[0], { name: 'fastify-helmet', url: 'https://github.com/fastify/fastify-helmet', owner: 'fastify', repo: 'fastify-helmet' }) assert.deepStrictEqual(links[1], { name: 'fastify-cors', url: 'https://github.com/fastify/fastify-cors', owner: 'fastify', repo: 'fastify-cors' }) }) it('extracts links with different owner/repo combinations', () => { const content = ` - [some-plugin](https://github.com/user123/awesome-plugin) - [another-lib](https://github.com/org-name/lib-name) ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 2) assert.strictEqual(links[0].owner, 'user123') assert.strictEqual(links[0].repo, 'awesome-plugin') assert.strictEqual(links[1].owner, 'org-name') assert.strictEqual(links[1].repo, 'lib-name') }) it('handles links with hash fragments', () => { const content = ` - [project](https://github.com/owner/repo#readme) ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 1) assert.strictEqual(links[0].repo, 'repo') assert.strictEqual(links[0].url, 'https://github.com/owner/repo#readme') }) it('handles links with query parameters', () => { const content = ` - [project](https://github.com/owner/repo?tab=readme) ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 1) assert.strictEqual(links[0].repo, 'repo') }) it('handles links with subpaths', () => { const content = ` - [docs](https://github.com/owner/repo/tree/main/docs) ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 1) assert.strictEqual(links[0].owner, 'owner') assert.strictEqual(links[0].repo, 'repo') }) it('returns empty array for content with no GitHub links', () => { const content = ` # No GitHub links here Just some regular text and [a link](https://example.com). ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 0) }) it('ignores non-GitHub links', () => { const content = ` - [gitlab](https://gitlab.com/owner/repo) - [github](https://github.com/owner/repo) - [bitbucket](https://bitbucket.org/owner/repo) ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 1) assert.strictEqual(links[0].owner, 'owner') }) it('extracts multiple links from complex markdown', () => { const content = ` ## Category 1 Some description [inline link](https://github.com/a/b). | Plugin | Description | |--------|-------------| | [plugin1](https://github.com/x/y) | Desc 1 | | [plugin2](https://github.com/z/w) | Desc 2 | ` const links = extractGitHubLinks(content) assert.strictEqual(links.length, 3) }) }) describe('checkGitHubRepo', () => { let originalDispatcher let mockAgent let originalSetTimeout beforeEach(() => { delete process.env.GITHUB_TOKEN originalDispatcher = getGlobalDispatcher() mockAgent = new MockAgent() mockAgent.disableNetConnect() setGlobalDispatcher(mockAgent) originalSetTimeout = global.setTimeout }) afterEach(async () => { global.setTimeout = originalSetTimeout setGlobalDispatcher(originalDispatcher) await mockAgent.close() delete process.env.GITHUB_TOKEN }) it('returns exists: true for status 200', async () => { const { checkGitHubRepo } = loadValidateEcosystemLinksModule() const mockPool = mockAgent.get('https://api.github.com') mockPool.intercept({ path: '/repos/fastify/fastify', method: 'HEAD' }).reply(200) const result = await checkGitHubRepo('fastify', 'fastify') assert.strictEqual(result.exists, true) assert.strictEqual(result.status, 200) assert.strictEqual(result.owner, 'fastify') assert.strictEqual(result.repo, 'fastify') }) it('returns exists: false for status 404', async () => { const { checkGitHubRepo } = loadValidateEcosystemLinksModule() const mockPool = mockAgent.get('https://api.github.com') mockPool.intercept({ path: '/repos/nonexistent/repo', method: 'HEAD' }).reply(404) const result = await checkGitHubRepo('nonexistent', 'repo') assert.strictEqual(result.exists, false) assert.strictEqual(result.status, 404) }) it('returns invalid status for malformed owner or repository names', async () => { const { checkGitHubRepo } = loadValidateEcosystemLinksModule() const result = await checkGitHubRepo('owner/evil', 'repo', 1) assert.strictEqual(result.exists, false) assert.strictEqual(result.status, 'invalid') assert.strictEqual(result.error, 'Invalid GitHub repository identifier') }) it('retries on rate limit responses', async () => { const { checkGitHubRepo } = loadValidateEcosystemLinksModule() let attempts = 0 global.setTimeout = (fn) => { fn(); return 0 } const mockPool = mockAgent.get('https://api.github.com') mockPool.intercept({ path: '/repos/owner/repo', method: 'HEAD' }).reply(() => { attempts++; return { statusCode: 403 } }) mockPool.intercept({ path: '/repos/owner/repo', method: 'HEAD' }).reply(() => { attempts++; return { statusCode: 200 } }) const result = await checkGitHubRepo('owner', 'repo', 1) assert.strictEqual(attempts, 2) assert.strictEqual(result.exists, true) assert.strictEqual(result.status, 200) }) it('adds authorization header when GITHUB_TOKEN is set', async () => { process.env.GITHUB_TOKEN = 'my-token' const { checkGitHubRepo } = loadValidateEcosystemLinksModule() const mockPool = mockAgent.get('https://api.github.com') mockPool.intercept({ path: '/repos/owner/repo', method: 'HEAD', headers: { authorization: 'token my-token' } }).reply(200) const result = await checkGitHubRepo('owner', 'repo') assert.strictEqual(result.exists, true) assert.strictEqual(result.status, 200) }) it('handles network errors', async () => { const { checkGitHubRepo } = loadValidateEcosystemLinksModule() const mockPool = mockAgent.get('https://api.github.com') mockPool.intercept({ path: '/repos/owner/repo', method: 'HEAD' }).replyWithError(new Error('Network error')) const result = await checkGitHubRepo('owner', 'repo') assert.strictEqual(result.exists, false) assert.strictEqual(result.status, 'error') assert.ok(result.error.length > 0) }) }) describe('validateAllLinks', () => { let originalReadFileSync let originalSetTimeout let originalConsoleLog let originalStdoutWrite let mockAgent let originalDispatcher beforeEach(() => { originalReadFileSync = fs.readFileSync originalSetTimeout = global.setTimeout originalConsoleLog = console.log originalStdoutWrite = process.stdout.write originalDispatcher = getGlobalDispatcher() mockAgent = new MockAgent() mockAgent.disableNetConnect() setGlobalDispatcher(mockAgent) console.log = () => {} process.stdout.write = () => true global.setTimeout = (fn) => { fn(); return 0 } }) afterEach(async () => { fs.readFileSync = originalReadFileSync global.setTimeout = originalSetTimeout console.log = originalConsoleLog process.stdout.write = originalStdoutWrite setGlobalDispatcher(originalDispatcher) await mockAgent.close() }) it('validates links, deduplicates repositories and groups inaccessible links', async () => { const { validateAllLinks } = loadValidateEcosystemLinksModule() let requests = 0 fs.readFileSync = () => ` - [repo one](https://github.com/owner/repo) - [repo one duplicate](https://github.com/owner/repo) - [repo two](https://github.com/another/project) ` const mockPool = mockAgent.get('https://api.github.com') mockPool.intercept({ path: '/repos/owner/repo', method: 'HEAD' }).reply(() => { requests++; return { statusCode: 404 } }) mockPool.intercept({ path: '/repos/another/project', method: 'HEAD' }).reply(() => { requests++; return { statusCode: 200 } }) const result = await validateAllLinks() assert.strictEqual(requests, 2) assert.strictEqual(result.notFound.length, 1) assert.strictEqual(result.found.length, 1) assert.strictEqual(result.notFound[0].owner, 'owner') assert.strictEqual(result.notFound[0].repo, 'repo') assert.strictEqual(result.found[0].owner, 'another') assert.strictEqual(result.found[0].repo, 'project') }) it('returns empty result when no GitHub links are present', async () => { const { validateAllLinks } = loadValidateEcosystemLinksModule() fs.readFileSync = () => '# Ecosystem\nNo links here.' const result = await validateAllLinks() assert.strictEqual(result.notFound.length, 0) assert.strictEqual(result.found.length, 0) }) })