@@ -37,7 +37,7 @@ describe('Secret Catalog', () => {
3737
3838 describe ( 'Icon validation' , ( ) => {
3939 it ( 'all icon values are valid SecretIconKey members' , ( ) => {
40- const validIcons : Set < SecretIconKey > = new Set ( [ 'send' , 'discord' , 'slack' , 'key' ] ) ;
40+ const validIcons : Set < SecretIconKey > = new Set ( [ 'send' , 'discord' , 'slack' , 'key' , 'github' ] ) ;
4141 for ( const entry of SECRET_CATALOG ) {
4242 expect ( validIcons . has ( entry . icon ) ) . toBe ( true ) ;
4343 }
@@ -108,6 +108,9 @@ describe('Secret Catalog', () => {
108108 'DISCORD_BOT_TOKEN' ,
109109 'SLACK_BOT_TOKEN' ,
110110 'SLACK_APP_TOKEN' ,
111+ 'GITHUB_TOKEN' ,
112+ 'GITHUB_USERNAME' ,
113+ 'GITHUB_EMAIL' ,
111114 ] ) ;
112115
113116 const catalogEnvVars = new Set ( FIELD_KEY_TO_ENV_VAR . values ( ) ) ;
@@ -122,6 +125,9 @@ describe('Secret Catalog', () => {
122125 expect ( FIELD_KEY_TO_ENV_VAR . get ( 'discordBotToken' ) ) . toBe ( 'DISCORD_BOT_TOKEN' ) ;
123126 expect ( FIELD_KEY_TO_ENV_VAR . get ( 'slackBotToken' ) ) . toBe ( 'SLACK_BOT_TOKEN' ) ;
124127 expect ( FIELD_KEY_TO_ENV_VAR . get ( 'slackAppToken' ) ) . toBe ( 'SLACK_APP_TOKEN' ) ;
128+ expect ( FIELD_KEY_TO_ENV_VAR . get ( 'githubToken' ) ) . toBe ( 'GITHUB_TOKEN' ) ;
129+ expect ( FIELD_KEY_TO_ENV_VAR . get ( 'githubUsername' ) ) . toBe ( 'GITHUB_USERNAME' ) ;
130+ expect ( FIELD_KEY_TO_ENV_VAR . get ( 'githubEmail' ) ) . toBe ( 'GITHUB_EMAIL' ) ;
125131 } ) ;
126132
127133 it ( 'ENV_VAR_TO_FIELD_KEY is the exact reverse of FIELD_KEY_TO_ENV_VAR' , ( ) => {
@@ -136,6 +142,9 @@ describe('Secret Catalog', () => {
136142 expect ( ENV_VAR_TO_FIELD_KEY . get ( 'DISCORD_BOT_TOKEN' ) ) . toBe ( 'discordBotToken' ) ;
137143 expect ( ENV_VAR_TO_FIELD_KEY . get ( 'SLACK_BOT_TOKEN' ) ) . toBe ( 'slackBotToken' ) ;
138144 expect ( ENV_VAR_TO_FIELD_KEY . get ( 'SLACK_APP_TOKEN' ) ) . toBe ( 'slackAppToken' ) ;
145+ expect ( ENV_VAR_TO_FIELD_KEY . get ( 'GITHUB_TOKEN' ) ) . toBe ( 'githubToken' ) ;
146+ expect ( ENV_VAR_TO_FIELD_KEY . get ( 'GITHUB_USERNAME' ) ) . toBe ( 'githubUsername' ) ;
147+ expect ( ENV_VAR_TO_FIELD_KEY . get ( 'GITHUB_EMAIL' ) ) . toBe ( 'githubEmail' ) ;
139148 } ) ;
140149 } ) ;
141150
@@ -160,6 +169,9 @@ describe('Secret Catalog', () => {
160169 expect ( FIELD_KEY_TO_ENTRY . get ( 'discordBotToken' ) ?. id ) . toBe ( 'discord' ) ;
161170 expect ( FIELD_KEY_TO_ENTRY . get ( 'slackBotToken' ) ?. id ) . toBe ( 'slack' ) ;
162171 expect ( FIELD_KEY_TO_ENTRY . get ( 'slackAppToken' ) ?. id ) . toBe ( 'slack' ) ;
172+ expect ( FIELD_KEY_TO_ENTRY . get ( 'githubToken' ) ?. id ) . toBe ( 'github' ) ;
173+ expect ( FIELD_KEY_TO_ENTRY . get ( 'githubUsername' ) ?. id ) . toBe ( 'github' ) ;
174+ expect ( FIELD_KEY_TO_ENTRY . get ( 'githubEmail' ) ?. id ) . toBe ( 'github' ) ;
163175 } ) ;
164176 } ) ;
165177
@@ -172,9 +184,15 @@ describe('Secret Catalog', () => {
172184 expect ( channels [ 2 ] . id ) . toBe ( 'slack' ) ;
173185 } ) ;
174186
175- it ( 'returns empty array for categories with no entries ' , ( ) => {
187+ it ( 'returns all tool entries sorted by order ' , ( ) => {
176188 const tools = getEntriesByCategory ( 'tool' ) ;
177- expect ( tools ) . toEqual ( [ ] ) ;
189+ expect ( tools . length ) . toBe ( 1 ) ;
190+ expect ( tools [ 0 ] . id ) . toBe ( 'github' ) ;
191+ } ) ;
192+
193+ it ( 'returns empty array for categories with no entries' , ( ) => {
194+ const providers = getEntriesByCategory ( 'provider' ) ;
195+ expect ( providers ) . toEqual ( [ ] ) ;
178196 } ) ;
179197 } ) ;
180198
@@ -188,8 +206,16 @@ describe('Secret Catalog', () => {
188206 expect ( keys . size ) . toBe ( 4 ) ;
189207 } ) ;
190208
191- it ( 'returns empty set for categories with no entries ' , ( ) => {
209+ it ( 'returns all tool field keys ' , ( ) => {
192210 const keys = getFieldKeysByCategory ( 'tool' ) ;
211+ expect ( keys ) . toContain ( 'githubToken' ) ;
212+ expect ( keys ) . toContain ( 'githubUsername' ) ;
213+ expect ( keys ) . toContain ( 'githubEmail' ) ;
214+ expect ( keys . size ) . toBe ( 3 ) ;
215+ } ) ;
216+
217+ it ( 'returns empty set for categories with no entries' , ( ) => {
218+ const keys = getFieldKeysByCategory ( 'provider' ) ;
193219 expect ( keys . size ) . toBe ( 0 ) ;
194220 } ) ;
195221 } ) ;
@@ -272,6 +298,56 @@ describe('Secret Catalog', () => {
272298 expect ( validateFieldValue ( 'xapp-short' , pattern ) ) . toBe ( false ) ;
273299 } ) ;
274300
301+ it ( 'accepts valid GitHub usernames' , ( ) => {
302+ const pattern = '^[a-zA-Z\\d](?:[a-zA-Z\\d]|-(?=[a-zA-Z\\d])){0,38}$' ;
303+ expect ( validateFieldValue ( 'octocat' , pattern ) ) . toBe ( true ) ;
304+ expect ( validateFieldValue ( 'my-bot-user' , pattern ) ) . toBe ( true ) ;
305+ expect ( validateFieldValue ( 'a' , pattern ) ) . toBe ( true ) ;
306+ expect ( validateFieldValue ( 'User123' , pattern ) ) . toBe ( true ) ;
307+ } ) ;
308+
309+ it ( 'rejects invalid GitHub usernames' , ( ) => {
310+ const pattern = '^[a-zA-Z\\d](?:[a-zA-Z\\d]|-(?=[a-zA-Z\\d])){0,38}$' ;
311+ expect ( validateFieldValue ( '-octocat' , pattern ) ) . toBe ( false ) ;
312+ expect ( validateFieldValue ( 'octocat-' , pattern ) ) . toBe ( false ) ;
313+ expect ( validateFieldValue ( 'my--name' , pattern ) ) . toBe ( false ) ;
314+ expect ( validateFieldValue ( 'my_name' , pattern ) ) . toBe ( false ) ;
315+ expect ( validateFieldValue ( 'user name' , pattern ) ) . toBe ( false ) ;
316+ } ) ;
317+
318+ it ( 'accepts valid email addresses' , ( ) => {
319+ const pattern = '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$' ;
320+ expect ( validateFieldValue ( 'bot@example.com' , pattern ) ) . toBe ( true ) ;
321+ expect ( validateFieldValue ( 'my-bot@my-org.io' , pattern ) ) . toBe ( true ) ;
322+ } ) ;
323+
324+ it ( 'rejects invalid email addresses' , ( ) => {
325+ const pattern = '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$' ;
326+ expect ( validateFieldValue ( 'notanemail' , pattern ) ) . toBe ( false ) ;
327+ expect ( validateFieldValue ( 'missing@domain' , pattern ) ) . toBe ( false ) ;
328+ expect ( validateFieldValue ( 'has space@example.com' , pattern ) ) . toBe ( false ) ;
329+ } ) ;
330+
331+ it ( 'accepts valid GitHub classic tokens (ghp_)' , ( ) => {
332+ const pattern = '^(ghp_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{22,255})$' ;
333+ expect ( validateFieldValue ( 'ghp_' + 'A' . repeat ( 36 ) , pattern ) ) . toBe ( true ) ;
334+ expect ( validateFieldValue ( 'ghp_' + 'abcDEF123456' . repeat ( 5 ) , pattern ) ) . toBe ( true ) ;
335+ } ) ;
336+
337+ it ( 'accepts valid GitHub fine-grained tokens (github_pat_)' , ( ) => {
338+ const pattern = '^(ghp_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{22,255})$' ;
339+ expect ( validateFieldValue ( 'github_pat_' + 'A' . repeat ( 22 ) , pattern ) ) . toBe ( true ) ;
340+ expect ( validateFieldValue ( 'github_pat_' + 'abc_DEF_123' . repeat ( 5 ) , pattern ) ) . toBe ( true ) ;
341+ } ) ;
342+
343+ it ( 'rejects invalid GitHub tokens' , ( ) => {
344+ const pattern = '^(ghp_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{22,255})$' ;
345+ expect ( validateFieldValue ( 'ghp_short' , pattern ) ) . toBe ( false ) ;
346+ expect ( validateFieldValue ( 'github_pat_short' , pattern ) ) . toBe ( false ) ;
347+ expect ( validateFieldValue ( 'gho_invalidprefix' , pattern ) ) . toBe ( false ) ;
348+ expect ( validateFieldValue ( 'invalid' , pattern ) ) . toBe ( false ) ;
349+ } ) ;
350+
275351 it ( 'rejects empty strings' , ( ) => {
276352 const pattern = '^\\d{8,}:[A-Za-z0-9_-]{30,50}$' ;
277353 expect ( validateFieldValue ( '' , pattern ) ) . toBe ( false ) ;
@@ -311,6 +387,21 @@ describe('Secret Catalog', () => {
311387 expect ( slack ?. fields . map ( f => f . key ) ) . toEqual ( [ 'slackBotToken' , 'slackAppToken' ] ) ;
312388 } ) ;
313389
390+ it ( 'github entry has allFieldsRequired set' , ( ) => {
391+ const github = SECRET_CATALOG_MAP . get ( 'github' ) ;
392+ expect ( github ?. allFieldsRequired ) . toBe ( true ) ;
393+ } ) ;
394+
395+ it ( 'github entry has exactly 3 fields' , ( ) => {
396+ const github = SECRET_CATALOG_MAP . get ( 'github' ) ;
397+ expect ( github ?. fields . length ) . toBe ( 3 ) ;
398+ expect ( github ?. fields . map ( f => f . key ) ) . toEqual ( [
399+ 'githubUsername' ,
400+ 'githubEmail' ,
401+ 'githubToken' ,
402+ ] ) ;
403+ } ) ;
404+
314405 it ( 'telegram and discord do not have allFieldsRequired' , ( ) => {
315406 expect ( SECRET_CATALOG_MAP . get ( 'telegram' ) ?. allFieldsRequired ) . toBeFalsy ( ) ;
316407 expect ( SECRET_CATALOG_MAP . get ( 'discord' ) ?. allFieldsRequired ) . toBeFalsy ( ) ;
0 commit comments