[READ-ONLY] a fast, modern browser for the npm registry
at main 186 lines 8.2 kB view raw
1import { describe, expect, it } from 'vitest' 2import { 3 validateUsername, 4 validateOrgName, 5 validateScopeTeam, 6 validatePackageName, 7 extractUrls, 8} from '../../../cli/src/npm-client.ts' 9 10describe('validateUsername', () => { 11 it('accepts valid usernames', () => { 12 expect(() => validateUsername('alice')).not.toThrow() 13 expect(() => validateUsername('bob123')).not.toThrow() 14 expect(() => validateUsername('my-user')).not.toThrow() 15 expect(() => validateUsername('user-name-123')).not.toThrow() 16 expect(() => validateUsername('a')).not.toThrow() 17 expect(() => validateUsername('A1')).not.toThrow() 18 }) 19 20 it('rejects empty or missing usernames', () => { 21 expect(() => validateUsername('')).toThrow('Invalid username') 22 expect(() => validateUsername(null as unknown as string)).toThrow('Invalid username') 23 expect(() => validateUsername(undefined as unknown as string)).toThrow('Invalid username') 24 }) 25 26 it('rejects usernames that are too long', () => { 27 const longName = 'a'.repeat(51) 28 expect(() => validateUsername(longName)).toThrow('Invalid username') 29 }) 30 31 it('rejects usernames with invalid characters', () => { 32 expect(() => validateUsername('user;rm -rf')).toThrow('Invalid username') 33 expect(() => validateUsername('user && evil')).toThrow('Invalid username') 34 expect(() => validateUsername('$(whoami)')).toThrow('Invalid username') 35 expect(() => validateUsername('user`id`')).toThrow('Invalid username') 36 expect(() => validateUsername('user|cat')).toThrow('Invalid username') 37 expect(() => validateUsername('user name')).toThrow('Invalid username') 38 expect(() => validateUsername('user.name')).toThrow('Invalid username') 39 expect(() => validateUsername('user_name')).toThrow('Invalid username') 40 expect(() => validateUsername('user@name')).toThrow('Invalid username') 41 }) 42 43 it('rejects usernames starting or ending with hyphen', () => { 44 expect(() => validateUsername('-username')).toThrow('Invalid username') 45 expect(() => validateUsername('username-')).toThrow('Invalid username') 46 expect(() => validateUsername('-')).toThrow('Invalid username') 47 }) 48}) 49 50describe('validateOrgName', () => { 51 it('accepts valid org names', () => { 52 expect(() => validateOrgName('nuxt')).not.toThrow() 53 expect(() => validateOrgName('my-org')).not.toThrow() 54 expect(() => validateOrgName('org123')).not.toThrow() 55 }) 56 57 it('rejects empty or missing org names', () => { 58 expect(() => validateOrgName('')).toThrow('Invalid org name') 59 }) 60 61 it('rejects org names that are too long', () => { 62 const longName = 'a'.repeat(51) 63 expect(() => validateOrgName(longName)).toThrow('Invalid org name') 64 }) 65 66 it('rejects org names with shell injection characters', () => { 67 expect(() => validateOrgName('org;rm -rf /')).toThrow('Invalid org name') 68 expect(() => validateOrgName('org && evil')).toThrow('Invalid org name') 69 expect(() => validateOrgName('$(whoami)')).toThrow('Invalid org name') 70 }) 71}) 72 73describe('validateScopeTeam', () => { 74 it('accepts valid scope:team format', () => { 75 expect(() => validateScopeTeam('@nuxt:developers')).not.toThrow() 76 expect(() => validateScopeTeam('@my-org:my-team')).not.toThrow() 77 expect(() => validateScopeTeam('@org123:team456')).not.toThrow() 78 expect(() => validateScopeTeam('@a:b')).not.toThrow() 79 }) 80 81 it('rejects empty or missing scope:team', () => { 82 expect(() => validateScopeTeam('')).toThrow('Invalid scope:team') 83 expect(() => validateScopeTeam(null as unknown as string)).toThrow('Invalid scope:team') 84 }) 85 86 it('rejects scope:team that is too long', () => { 87 const longScopeTeam = '@' + 'a'.repeat(50) + ':' + 'b'.repeat(50) 88 expect(() => validateScopeTeam(longScopeTeam)).toThrow('Invalid scope:team') 89 }) 90 91 it('rejects invalid scope:team format', () => { 92 expect(() => validateScopeTeam('nuxt:developers')).toThrow('Invalid scope:team format') 93 expect(() => validateScopeTeam('@nuxt')).toThrow('Invalid scope:team format') 94 expect(() => validateScopeTeam('developers')).toThrow('Invalid scope:team format') 95 expect(() => validateScopeTeam('@:team')).toThrow('Invalid scope:team format') 96 expect(() => validateScopeTeam('@org:')).toThrow('Invalid scope:team format') 97 }) 98 99 it('rejects scope:team with shell injection in scope', () => { 100 expect(() => validateScopeTeam('@org;rm:team')).toThrow('Invalid scope:team format') 101 expect(() => validateScopeTeam('@$(whoami):team')).toThrow('Invalid scope:team format') 102 }) 103 104 it('rejects scope:team with shell injection in team', () => { 105 expect(() => validateScopeTeam('@org:team;rm')).toThrow('Invalid scope:team format') 106 expect(() => validateScopeTeam('@org:$(whoami)')).toThrow('Invalid scope:team format') 107 }) 108 109 it('rejects scope or team starting/ending with hyphen', () => { 110 expect(() => validateScopeTeam('@-org:team')).toThrow('Invalid scope:team format') 111 expect(() => validateScopeTeam('@org-:team')).toThrow('Invalid scope:team format') 112 expect(() => validateScopeTeam('@org:-team')).toThrow('Invalid scope:team format') 113 expect(() => validateScopeTeam('@org:team-')).toThrow('Invalid scope:team format') 114 }) 115}) 116 117describe('validatePackageName', () => { 118 it('accepts valid package names', () => { 119 expect(() => validatePackageName('my-package')).not.toThrow() 120 expect(() => validatePackageName('@scope/package')).not.toThrow() 121 expect(() => validatePackageName('package123')).not.toThrow() 122 }) 123 124 it('rejects package names with shell injection', () => { 125 expect(() => validatePackageName('pkg;rm -rf /')).toThrow('Invalid package name') 126 expect(() => validatePackageName('pkg && evil')).toThrow('Invalid package name') 127 expect(() => validatePackageName('$(whoami)')).toThrow('Invalid package name') 128 }) 129 130 it('rejects empty package names', () => { 131 expect(() => validatePackageName('')).toThrow('Invalid package name') 132 }) 133}) 134 135describe('extractUrls', () => { 136 it('extracts HTTP URLs from text', () => { 137 const text = 'Visit http://example.com for more info' 138 expect(extractUrls(text)).toEqual(['http://example.com']) 139 }) 140 141 it('extracts HTTPS URLs from text', () => { 142 const text = 'Visit https://example.com/path for more info' 143 expect(extractUrls(text)).toEqual(['https://example.com/path']) 144 }) 145 146 it('extracts multiple URLs from text', () => { 147 const text = 'See https://example.com and http://other.org/page' 148 expect(extractUrls(text)).toEqual(['https://example.com', 'http://other.org/page']) 149 }) 150 151 it('strips trailing punctuation from URLs', () => { 152 expect(extractUrls('Go to https://example.com.')).toEqual(['https://example.com']) 153 expect(extractUrls('Go to https://example.com,')).toEqual(['https://example.com']) 154 expect(extractUrls('Go to https://example.com;')).toEqual(['https://example.com']) 155 expect(extractUrls('Go to https://example.com:')).toEqual(['https://example.com']) 156 expect(extractUrls('Go to https://example.com!')).toEqual(['https://example.com']) 157 expect(extractUrls('Go to https://example.com?')).toEqual(['https://example.com']) 158 expect(extractUrls('Go to https://example.com)')).toEqual(['https://example.com']) 159 }) 160 161 it('strips multiple trailing punctuation characters', () => { 162 expect(extractUrls('See https://example.com/path).')).toEqual(['https://example.com/path']) 163 }) 164 165 it('preserves query strings and fragments', () => { 166 expect(extractUrls('Go to https://example.com/path?q=1&b=2#anchor')).toEqual([ 167 'https://example.com/path?q=1&b=2#anchor', 168 ]) 169 }) 170 171 it('returns empty array when no URLs found', () => { 172 expect(extractUrls('No URLs here')).toEqual([]) 173 expect(extractUrls('')).toEqual([]) 174 }) 175 176 it('deduplicates identical URLs', () => { 177 const text = 'Visit https://example.com and again https://example.com' 178 expect(extractUrls(text)).toEqual(['https://example.com']) 179 }) 180 181 it('extracts URLs from npm auth output', () => { 182 const npmOutput = 183 'Authenticate your account at:\nhttps://www.npmjs.com/login?next=/login/cli/abc123' 184 expect(extractUrls(npmOutput)).toEqual(['https://www.npmjs.com/login?next=/login/cli/abc123']) 185 }) 186})