[READ-ONLY] a fast, modern browser for the npm registry
at main 261 lines 8.5 kB view raw
1import { describe, expect, it, vi, beforeEach } from 'vitest' 2import { parsePackageParams } from '../../../../server/utils/parse-package-params' 3import { NPM_MISSING_README_SENTINEL } from '#shared/utils/constants' 4 5// Mock Nitro globals before importing the module 6vi.stubGlobal('defineCachedFunction', (fn: Function) => fn) 7const $fetchMock = vi.fn() 8vi.stubGlobal('$fetch', $fetchMock) 9vi.stubGlobal('parsePackageParams', parsePackageParams) 10 11const fetchNpmPackageMock = vi.fn() 12vi.stubGlobal('fetchNpmPackage', fetchNpmPackageMock) 13 14const parseRepositoryInfoMock = vi.fn() 15vi.stubGlobal('parseRepositoryInfo', parseRepositoryInfoMock) 16 17const { fetchReadmeFromJsdelivr, isStandardReadme, resolvePackageReadmeSource } = 18 await import('../../../../server/utils/readme-loaders') 19 20describe('isStandardReadme', () => { 21 it('returns true for standard README filenames', () => { 22 expect(isStandardReadme('README.md')).toBe(true) 23 expect(isStandardReadme('readme.md')).toBe(true) 24 expect(isStandardReadme('Readme.md')).toBe(true) 25 expect(isStandardReadme('README')).toBe(true) 26 expect(isStandardReadme('readme')).toBe(true) 27 expect(isStandardReadme('README.markdown')).toBe(true) 28 expect(isStandardReadme('readme.markdown')).toBe(true) 29 }) 30 31 it('returns false for non-standard filenames', () => { 32 expect(isStandardReadme('CONTRIBUTING.md')).toBe(false) 33 expect(isStandardReadme('README.txt')).toBe(false) 34 expect(isStandardReadme('readme.rst')).toBe(false) 35 expect(isStandardReadme(undefined)).toBe(false) 36 expect(isStandardReadme('')).toBe(false) 37 }) 38}) 39 40describe('fetchReadmeFromJsdelivr', () => { 41 it('returns content when first filename succeeds', async () => { 42 const content = '# Package' 43 const fetchMock = vi.fn().mockResolvedValue({ 44 ok: true, 45 text: async () => content, 46 }) 47 vi.stubGlobal('fetch', fetchMock) 48 49 const result = await fetchReadmeFromJsdelivr('some-pkg', ['README.md']) 50 51 expect(result).toBe(content) 52 expect(fetchMock).toHaveBeenCalledWith('https://cdn.jsdelivr.net/npm/some-pkg/README.md') 53 }) 54 55 it('includes version in URL when version is passed', async () => { 56 const fetchMock = vi.fn().mockResolvedValue({ 57 ok: true, 58 text: async () => '', 59 }) 60 vi.stubGlobal('fetch', fetchMock) 61 62 await fetchReadmeFromJsdelivr('pkg', ['README.md'], '1.2.3') 63 64 expect(fetchMock).toHaveBeenCalledWith('https://cdn.jsdelivr.net/npm/pkg@1.2.3/README.md') 65 }) 66 67 it('returns null when all fetches fail', async () => { 68 const fetchMock = vi.fn().mockResolvedValue({ ok: false }) 69 vi.stubGlobal('fetch', fetchMock) 70 71 const result = await fetchReadmeFromJsdelivr('pkg', ['README.md', 'readme.md']) 72 73 expect(result).toBeNull() 74 expect(fetchMock).toHaveBeenCalledTimes(2) 75 }) 76}) 77 78describe('resolvePackageReadmeSource', () => { 79 beforeEach(() => { 80 fetchNpmPackageMock.mockReset() 81 parseRepositoryInfoMock.mockReset() 82 }) 83 84 it('returns markdown and repoInfo when package has valid npm readme (latest)', async () => { 85 const markdown = '# Hello' 86 fetchNpmPackageMock.mockResolvedValue({ 87 readme: markdown, 88 readmeFilename: 'README.md', 89 repository: { url: 'https://github.com/u/r' }, 90 versions: {}, 91 }) 92 parseRepositoryInfoMock.mockReturnValue({ 93 provider: 'github', 94 owner: 'u', 95 repo: 'r', 96 rawBaseUrl: 'https://raw.githubusercontent.com/u/r/HEAD', 97 blobBaseUrl: 'https://github.com/u/r/blob/HEAD', 98 }) 99 100 const result = await resolvePackageReadmeSource('some-pkg') 101 102 expect(result).toMatchObject({ 103 packageName: 'some-pkg', 104 version: undefined, 105 markdown, 106 repoInfo: { provider: 'github', owner: 'u', repo: 'r' }, 107 }) 108 expect(fetchNpmPackageMock).toHaveBeenCalledWith('some-pkg') 109 }) 110 111 it('returns markdown from version when packagePath includes version', async () => { 112 const markdown = '# Version readme' 113 fetchNpmPackageMock.mockResolvedValue({ 114 readme: 'latest readme', 115 readmeFilename: 'README.md', 116 repository: undefined, 117 versions: { 118 '1.0.0': { readme: markdown, readmeFilename: 'README.md' }, 119 }, 120 }) 121 parseRepositoryInfoMock.mockReturnValue(undefined) 122 123 const result = await resolvePackageReadmeSource('some-pkg/v/1.0.0') 124 125 expect(result).toMatchObject({ 126 packageName: 'some-pkg', 127 version: '1.0.0', 128 markdown, 129 }) 130 }) 131 132 it('falls back to jsdelivr when npm readme is missing sentinel', async () => { 133 const jsdelivrContent = '# From CDN' 134 fetchNpmPackageMock.mockResolvedValue({ 135 readme: NPM_MISSING_README_SENTINEL, 136 readmeFilename: 'README.md', 137 repository: undefined, 138 versions: {}, 139 }) 140 parseRepositoryInfoMock.mockReturnValue(undefined) 141 const fetchMock = vi.fn().mockResolvedValue({ 142 ok: true, 143 text: async () => jsdelivrContent, 144 }) 145 vi.stubGlobal('fetch', fetchMock) 146 147 const result = await resolvePackageReadmeSource('pkg') 148 149 expect(result).toMatchObject({ 150 packageName: 'pkg', 151 markdown: jsdelivrContent, 152 repoInfo: undefined, 153 }) 154 expect(fetchMock).toHaveBeenCalled() 155 }) 156 157 it('falls back to jsdelivr when readmeFilename is not standard', async () => { 158 const jsdelivrContent = '# From CDN' 159 fetchNpmPackageMock.mockResolvedValue({ 160 readme: 'content', 161 readmeFilename: 'DOCS.md', 162 repository: undefined, 163 versions: {}, 164 }) 165 parseRepositoryInfoMock.mockReturnValue(undefined) 166 const fetchMock = vi.fn().mockResolvedValue({ 167 ok: true, 168 text: async () => jsdelivrContent, 169 }) 170 vi.stubGlobal('fetch', fetchMock) 171 172 const result = await resolvePackageReadmeSource('pkg') 173 174 expect(result).toMatchObject({ markdown: jsdelivrContent }) 175 }) 176 177 it('returns undefined markdown when no content and jsdelivr fails', async () => { 178 fetchNpmPackageMock.mockResolvedValue({ 179 readme: undefined, 180 readmeFilename: undefined, 181 repository: undefined, 182 versions: {}, 183 }) 184 parseRepositoryInfoMock.mockReturnValue(undefined) 185 const fetchMock = vi.fn().mockResolvedValue({ ok: false }) 186 vi.stubGlobal('fetch', fetchMock) 187 188 const result = await resolvePackageReadmeSource('pkg') 189 190 expect(result).toMatchObject({ 191 packageName: 'pkg', 192 version: undefined, 193 markdown: undefined, 194 repoInfo: undefined, 195 }) 196 }) 197 198 it('returns undefined markdown when content is NPM_MISSING_README_SENTINEL and jsdelivr fails', async () => { 199 fetchNpmPackageMock.mockResolvedValue({ 200 readme: NPM_MISSING_README_SENTINEL, 201 readmeFilename: 'README.md', 202 repository: undefined, 203 versions: {}, 204 }) 205 const fetchMock = vi.fn().mockResolvedValue({ ok: false }) 206 vi.stubGlobal('fetch', fetchMock) 207 208 const result = await resolvePackageReadmeSource('pkg') 209 210 expect(result).toMatchObject({ 211 packageName: 'pkg', 212 markdown: undefined, 213 repoInfo: undefined, 214 }) 215 }) 216 217 it('fetches from jsdelivr when packument readme exceeds truncation threshold', async () => { 218 const truncatedReadme = 'x'.repeat(64_000) 219 const fullReadme = 'x'.repeat(80_000) 220 fetchNpmPackageMock.mockResolvedValue({ 221 'readme': truncatedReadme, 222 'readmeFilename': 'README.md', 223 'repository': undefined, 224 'versions': {}, 225 'dist-tags': { latest: '1.0.0' }, 226 }) 227 parseRepositoryInfoMock.mockReturnValue(undefined) 228 const fetchMock = vi.fn().mockResolvedValue({ 229 ok: true, 230 text: async () => fullReadme, 231 }) 232 vi.stubGlobal('fetch', fetchMock) 233 234 const result = await resolvePackageReadmeSource('pkg') 235 236 expect(result).toMatchObject({ markdown: fullReadme }) 237 expect(fetchMock).toHaveBeenCalled() 238 }) 239 240 it('uses package repository for repoInfo when markdown is present', async () => { 241 fetchNpmPackageMock.mockResolvedValue({ 242 readme: '# Hi', 243 readmeFilename: 'README.md', 244 repository: { url: 'https://github.com/a/b' }, 245 versions: {}, 246 }) 247 const repoInfo = { 248 provider: 'github' as const, 249 owner: 'a', 250 repo: 'b', 251 rawBaseUrl: 'https://raw.githubusercontent.com/a/b/HEAD', 252 blobBaseUrl: 'https://github.com/a/b/blob/HEAD', 253 } 254 parseRepositoryInfoMock.mockReturnValue(repoInfo) 255 256 const result = await resolvePackageReadmeSource('pkg') 257 258 expect(result?.repoInfo).toEqual(repoInfo) 259 expect(parseRepositoryInfoMock).toHaveBeenCalledWith({ url: 'https://github.com/a/b' }) 260 }) 261})