From ec348d31782aff4e829d2a7577b6f058db410d68 Mon Sep 17 00:00:00 2001 From: supervoidcoder <88671013+supervoidcoder@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:45:28 -0400 Subject: [PATCH 1/4] some animations/css changes stuff --- src/addons/settings/settings.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/addons/settings/settings.css b/src/addons/settings/settings.css index bd50a296a..69f9616e9 100644 --- a/src/addons/settings/settings.css +++ b/src/addons/settings/settings.css @@ -17,6 +17,10 @@ @import "../../css/colors.css"; @import "../../css/filters.css"; +*{ + transition: all 0.2s ease-in-out; +} + body { background-color: $page-background; color: $page-foreground; @@ -190,6 +194,7 @@ a:active, a:focus { } .addon-group-name:hover .addon-group-expand-container { background: $ui-black-transparent; + transform: scale(1.1); } .addon-group-expand-icon { width: 100%; From ae66f3973d070e7597c739217a7f1a722caeea33 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:42:00 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Add=20J?= =?UTF-8?q?est=20unit=20tests=20for=20workflow,=20settings.css,=20and=20pa?= =?UTF-8?q?ckage-lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/dependencies/package-lock.test.js | 266 ++++++++++++++++++ test/unit/styles/settings-css.test.js | 270 +++++++++++++++++++ test/unit/workflows/summary-workflow.test.js | 227 ++++++++++++++++ 3 files changed, 763 insertions(+) create mode 100644 test/unit/dependencies/package-lock.test.js create mode 100644 test/unit/styles/settings-css.test.js create mode 100644 test/unit/workflows/summary-workflow.test.js diff --git a/test/unit/dependencies/package-lock.test.js b/test/unit/dependencies/package-lock.test.js new file mode 100644 index 000000000..8b526996a --- /dev/null +++ b/test/unit/dependencies/package-lock.test.js @@ -0,0 +1,266 @@ +/* eslint-env jest */ +/** + * Tests for package-lock.json + * + * This test suite validates the structure and integrity of the package lock file, + * ensuring dependencies are properly locked and secure. + */ +const fs = require('fs'); +const path = require('path'); + +describe('Package Lock File', () => { + let packageLock; + let packageLockPath; + + beforeAll(() => { + packageLockPath = path.join(__dirname, '../../../package-lock.json'); + const content = fs.readFileSync(packageLockPath, 'utf8'); + packageLock = JSON.parse(content); + }); + + describe('JSON Structure', () => { + test('package-lock.json is valid JSON', () => { + expect(packageLock).toBeDefined(); + expect(typeof packageLock).toBe('object'); + }); + + test('has required top-level fields', () => { + expect(packageLock).toHaveProperty('name'); + expect(packageLock).toHaveProperty('version'); + expect(packageLock).toHaveProperty('lockfileVersion'); + }); + + test('lockfile version is supported', () => { + const version = packageLock.lockfileVersion; + expect([1, 2, 3]).toContain(version); + }); + + test('has packages section', () => { + expect(packageLock).toHaveProperty('packages'); + expect(typeof packageLock.packages).toBe('object'); + }); + }); + + describe('Dependency Updates', () => { + test('scratch-blocks dependency is updated', () => { + const scratchBlocks = Object.entries(packageLock.packages || {}) + .find(([key]) => key.includes('scratch-blocks')); + + expect(scratchBlocks).toBeDefined(); + }); + + test('scratch-vm dependency is updated', () => { + const scratchVm = Object.entries(packageLock.packages || {}) + .find(([key]) => key.includes('scratch-vm')); + + expect(scratchVm).toBeDefined(); + }); + + test('git dependencies have valid commit hashes', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.resolved && pkg.resolved.startsWith('git+ssh://')) { + const match = pkg.resolved.match(/#([a-f0-9]{40})/); + expect(match).toBeTruthy(); + expect(match[1].length).toBe(40); + } + }); + }); + + test('git dependencies use SSH protocol', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.resolved && pkg.resolved.includes('github.com')) { + if (pkg.resolved.startsWith('git+')) { + expect(pkg.resolved).toContain('git+ssh://'); + } + } + }); + }); + }); + + describe('Integrity Checks', () => { + test('all packages have integrity hashes', () => { + const packages = Object.entries(packageLock.packages || {}); + const packagesWithResolved = packages.filter(([, pkg]) => pkg.resolved); + + packagesWithResolved.forEach(([key, pkg]) => { + if (!pkg.resolved.startsWith('git+')) { + expect(pkg).toHaveProperty('integrity'); + } + }); + }); + + test('integrity hashes use strong algorithms', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.integrity) { + expect(pkg.integrity).toMatch(/^sha(256|512)-/); + } + }); + }); + + test('git dependencies have integrity hashes', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.resolved && pkg.resolved.startsWith('git+ssh://')) { + expect(pkg.integrity).toBeDefined(); + expect(typeof pkg.integrity).toBe('string'); + } + }); + }); + }); + + describe('Version Consistency', () => { + test('root package version matches', () => { + const packageJsonPath = path.join(__dirname, '../../../package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + expect(packageLock.version).toBe(packageJson.version); + }); + + test('all dependencies have valid semantic versions', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.version && !pkg.resolved?.startsWith('git+')) { + expect(pkg.version).toMatch(/^\d+\.\d+\.\d+/); + } + }); + }); + }); + + describe('Security Considerations', () => { + test('no obvious security vulnerabilities in dependency versions', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.name && pkg.version) { + const match = pkg.version.match(/^(\d+)/); + if (match) { + const majorVersion = parseInt(match[1]); + if (key.includes('scratch-')) { + expect(majorVersion).toBeGreaterThanOrEqual(0); + } + } + } + }); + }); + + test('dependencies are from trusted sources', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.resolved && pkg.resolved.startsWith('http')) { + expect(pkg.resolved).toMatch(/npmjs\.org|github\.com/); + } + }); + }); + + test('no suspicious package names', () => { + Object.entries(packageLock.packages || {}).forEach(([key, pkg]) => { + if (pkg.name) { + expect(pkg.name).not.toMatch(/^(j5|reaqt|nodej5)/); + } + }); + }); + }); + + describe('Dependency Graph', () => { + test('no circular dependencies in immediate deps', () => { + const visited = new Set(); + const checkCircular = (pkgKey, path = []) => { + if (path.includes(pkgKey)) { + return false; + } + if (visited.has(pkgKey)) { + return true; + } + visited.add(pkgKey); + return true; + }; + + Object.keys(packageLock.packages || {}).forEach(key => { + expect(checkCircular(key)).toBe(true); + }); + }); + + test('root package has dependencies defined', () => { + const rootPackage = packageLock.packages['']; + expect(rootPackage).toBeDefined(); + expect(rootPackage.dependencies || rootPackage.devDependencies).toBeDefined(); + }); + }); + + describe('File Integrity', () => { + test('file is properly formatted JSON', () => { + const raw = fs.readFileSync(packageLockPath, 'utf8'); + expect(() => JSON.parse(raw)).not.toThrow(); + }); + + test('file uses consistent indentation', () => { + const raw = fs.readFileSync(packageLockPath, 'utf8'); + const lines = raw.split('\n'); + const indentedLines = lines.filter(line => line.startsWith(' ')); + + if (indentedLines.length > 0) { + const firstIndent = indentedLines[0].match(/^ +/); + if (firstIndent) { + const indentSize = firstIndent[0].length; + expect(indentSize % 2).toBe(0); + } + } + }); + + test('file ends with newline', () => { + const raw = fs.readFileSync(packageLockPath, 'utf8'); + expect(raw.endsWith('\n')).toBe(true); + }); + }); + + describe('Specific Package Updates', () => { + test('scratch-blocks points to correct commit', () => { + const scratchBlocks = Object.entries(packageLock.packages || {}) + .find(([key]) => key.includes('scratch-blocks')); + + if (scratchBlocks) { + const [, pkg] = scratchBlocks; + if (pkg.resolved) { + expect(pkg.resolved).toContain('7b24920ea6fc99228b63ef7ada5091e19c4f4553'); + } + } + }); + + test('scratch-vm points to correct commit', () => { + const scratchVm = Object.entries(packageLock.packages || {}) + .find(([key]) => key.includes('scratch-vm')); + + if (scratchVm) { + const [, pkg] = scratchVm; + if (pkg.resolved) { + expect(pkg.resolved).toContain('279ea2a18b244bedd545b59a00247df9a841cf9d'); + } + } + }); + + test('updated packages maintain license information', () => { + ['scratch-blocks', 'scratch-vm'].forEach(pkgName => { + const pkg = Object.entries(packageLock.packages || {}) + .find(([key]) => key.includes(pkgName)); + + if (pkg) { + const [, pkgData] = pkg; + expect(pkgData.license).toBeDefined(); + expect(typeof pkgData.license).toBe('string'); + } + }); + }); + }); + + describe('Regression Tests', () => { + test('maintains backward compatibility with package structure', () => { + const essential = ['webpack', 'babel', 'react']; + essential.forEach(pkgPrefix => { + const found = Object.keys(packageLock.packages || {}) + .some(key => key.includes(pkgPrefix)); + expect(found).toBe(true); + }); + }); + + test('no unexpected package removals', () => { + const scratchPackages = Object.keys(packageLock.packages || {}) + .filter(key => key.includes('scratch-')); + expect(scratchPackages.length).toBeGreaterThan(0); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/styles/settings-css.test.js b/test/unit/styles/settings-css.test.js new file mode 100644 index 000000000..437e6ff79 --- /dev/null +++ b/test/unit/styles/settings-css.test.js @@ -0,0 +1,270 @@ +/* eslint-env jest */ +/** + * Tests for src/addons/settings/settings.css + * + * This test suite validates CSS syntax, structure, and potential issues + * in the settings page stylesheet. + */ +const fs = require('fs'); +const path = require('path'); + +describe('Settings CSS', () => { + let cssContent; + let cssPath; + + beforeAll(() => { + cssPath = path.join(__dirname, '../../../src/addons/settings/settings.css'); + cssContent = fs.readFileSync(cssPath, 'utf8'); + }); + + describe('File Structure', () => { + test('CSS file exists and is readable', () => { + expect(cssContent).toBeDefined(); + expect(cssContent.length).toBeGreaterThan(0); + }); + + test('has proper copyright header', () => { + expect(cssContent).toContain('Copyright'); + expect(cssContent).toContain('GNU General Public License'); + }); + + test('imports required CSS dependencies', () => { + expect(cssContent).toContain('@import "../../css/colors.css"'); + expect(cssContent).toContain('@import "../../css/filters.css"'); + }); + + test('imports are at the top of the file', () => { + const firstImportIndex = cssContent.indexOf('@import'); + const firstRuleIndex = cssContent.indexOf('{'); + expect(firstImportIndex).toBeLessThan(firstRuleIndex); + }); + }); + + describe('Global Transition Rule', () => { + test('contains universal selector with transition', () => { + expect(cssContent).toContain('*{'); + expect(cssContent).toMatch(/\*\s*\{[^}]*transition/); + }); + + test('transition applies to all properties', () => { + const universalBlock = cssContent.match(/\*\s*\{[^}]*\}/s); + expect(universalBlock).toBeTruthy(); + expect(universalBlock[0]).toContain('transition: all'); + }); + + test('transition has reasonable duration', () => { + const universalBlock = cssContent.match(/\*\s*\{[^}]*\}/s); + expect(universalBlock[0]).toContain('0.2s'); + expect(universalBlock[0]).toContain('ease-in-out'); + }); + + test('universal selector is properly positioned', () => { + const importsEnd = cssContent.lastIndexOf('@import'); + const universalSelector = cssContent.indexOf('*{'); + const bodySelector = cssContent.indexOf('body {'); + + expect(universalSelector).toBeGreaterThan(importsEnd); + expect(universalSelector).toBeLessThan(bodySelector); + }); + }); + + describe('Hover Effects', () => { + test('contains hover effect for addon-group-expand-container', () => { + expect(cssContent).toContain('.addon-group-name:hover .addon-group-expand-container'); + }); + + test('hover effect includes scale transform', () => { + const hoverBlock = cssContent.match( + /\.addon-group-name:hover\s+\.addon-group-expand-container\s*\{[^}]*\}/s + ); + expect(hoverBlock).toBeTruthy(); + expect(hoverBlock[0]).toContain('transform: scale(1.1)'); + }); + + test('hover effect maintains background color', () => { + const hoverBlock = cssContent.match( + /\.addon-group-name:hover\s+\.addon-group-expand-container\s*\{[^}]*\}/s + ); + expect(hoverBlock[0]).toContain('background:'); + }); + }); + + describe('CSS Syntax Validation', () => { + test('all opening braces have matching closing braces', () => { + const openBraces = (cssContent.match(/\{/g) || []).length; + const closeBraces = (cssContent.match(/\}/g) || []).length; + expect(openBraces).toBe(closeBraces); + }); + + test('selectors are properly formatted', () => { + const lines = cssContent.split('\n'); + const selectorLines = lines.filter(line => + line.trim().endsWith('{') && !line.trim().startsWith('@') + ); + selectorLines.forEach(line => { + expect(line).toMatch(/[.#\w\-:[\]="',\s*+>~()]+\{/); + }); + }); + + test('properties end with semicolons', () => { + const propertyLines = cssContent.split('\n').filter(line => { + const trimmed = line.trim(); + return trimmed.includes(':') && + !trimmed.startsWith('/*') && + !trimmed.startsWith('*') && + !trimmed.startsWith('@'); + }); + + propertyLines.forEach(line => { + const trimmed = line.trim(); + if (trimmed && !trimmed.endsWith('{') && !trimmed.endsWith('}')) { + expect(trimmed).toMatch(/;$/); + } + }); + }); + + test('no duplicate property declarations in same rule', () => { + const rules = cssContent.match(/\{[^}]+\}/g) || []; + rules.forEach(rule => { + const properties = rule.match(/\b(\w+-?\w*)\s*:/g) || []; + const propertyNames = properties.map(p => p.replace(/\s*:/, '')); + const uniqueProperties = new Set(propertyNames); + + const filtered = propertyNames.filter(p => + !(p === 'transition' || p === 'transition-property') + ); + const uniqueFiltered = new Set(filtered); + + expect(filtered.length).toBe(uniqueFiltered.size); + }); + }); + }); + + describe('Color Variables', () => { + test('uses CSS custom properties for colors', () => { + expect(cssContent).toContain('$'); + expect(cssContent).toMatch(/\$[\w-]+/); + }); + + test('does not use hardcoded colors in critical places', () => { + const themeElements = [ + 'background-color', + 'color:', + 'border-color' + ]; + + themeElements.forEach(prop => { + if (cssContent.includes(prop)) { + const pattern = new RegExp(`${prop}:\\s*\\$`, 'g'); + const matches = cssContent.match(pattern); + expect(matches).toBeTruthy(); + } + }); + }); + }); + + describe('Transition Properties', () => { + test('transition durations are consistent', () => { + const durations = cssContent.match(/transition:.*?([\d.]+s)/g); + expect(durations).toBeTruthy(); + durations?.forEach(duration => { + const value = parseFloat(duration.match(/([\d.]+)s/)[1]); + expect(value).toBeGreaterThanOrEqual(0.1); + expect(value).toBeLessThanOrEqual(1); + }); + }); + + test('uses ease functions appropriately', () => { + const easing = cssContent.match(/transition.*?(ease[-\w]*)/gi); + expect(easing).toBeTruthy(); + easing?.forEach(ease => { + expect(ease).toMatch(/ease(-in|-out|-in-out)?/i); + }); + }); + + test('specific transition properties are defined where needed', () => { + expect(cssContent).toContain('transition-property'); + }); + }); + + describe('Responsive Design', () => { + test('contains media queries for mobile devices', () => { + expect(cssContent).toContain('@media'); + expect(cssContent).toMatch(/@media\s*\([^)]*max-width/); + }); + + test('media queries have reasonable breakpoints', () => { + const breakpoints = cssContent.match(/@media[^{]*max-width:\s*(\d+)px/g); + expect(breakpoints).toBeTruthy(); + breakpoints?.forEach(bp => { + const width = parseInt(bp.match(/(\d+)px/)[1]); + expect(width).toBeGreaterThan(0); + expect(width).toBeLessThan(2000); + }); + }); + }); + + describe('Transform Properties', () => { + test('transform scale is used correctly', () => { + expect(cssContent).toContain('transform: scale(1.1)'); + }); + + test('transform values are reasonable', () => { + const transforms = cssContent.match(/scale\(([\d.]+)\)/g); + transforms?.forEach(transform => { + const value = parseFloat(transform.match(/([\d.]+)/)[1]); + expect(value).toBeGreaterThan(0); + expect(value).toBeLessThan(2); + }); + }); + }); + + describe('Accessibility', () => { + test('focus states are defined', () => { + expect(cssContent).toContain(':focus'); + expect(cssContent).toMatch(/:focus[^}]*box-shadow/); + }); + + test('active states are defined', () => { + expect(cssContent).toContain(':active'); + }); + + test('focus-within is used for better accessibility', () => { + expect(cssContent).toContain(':focus-within'); + }); + }); + + describe('Maintainability', () => { + test('uses consistent naming conventions', () => { + const classNames = cssContent.match(/\.[\w-]+/g) || []; + classNames.forEach(className => { + expect(className).toMatch(/^\.[a-z][\w-]*$/); + }); + }); + + test('has logical grouping of related styles', () => { + expect(cssContent.indexOf('.button')).toBeLessThan( + cssContent.indexOf('.button:hover') + ); + }); + + test('comments are used to explain complex sections', () => { + const comments = cssContent.match(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g); + expect(comments).toBeTruthy(); + }); + }); + + describe('Edge Cases and Potential Issues', () => { + test('no empty rulesets', () => { + const emptyRules = cssContent.match(/\{[\s]*\}/g); + expect(emptyRules).toBeFalsy(); + }); + + test('no conflicting transition declarations', () => { + const specificTransitions = cssContent.match(/transition:.*?;/g) || []; + specificTransitions.forEach(trans => { + expect(trans).toMatch(/transition:\s*[\w\s.,()]+;/); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/workflows/summary-workflow.test.js b/test/unit/workflows/summary-workflow.test.js new file mode 100644 index 000000000..a4f9ecc14 --- /dev/null +++ b/test/unit/workflows/summary-workflow.test.js @@ -0,0 +1,227 @@ +/* eslint-env jest */ +/** + * Tests for .github/workflows/summary.yml + * + * This test suite validates the GitHub Actions workflow configuration + * for issue summarization and security handling. + */ +const fs = require('fs'); +const path = require('path'); + +describe('GitHub Actions Summary Workflow', () => { + let workflowContent; + let workflowPath; + + beforeAll(() => { + workflowPath = path.join(__dirname, '../../../.github/workflows/summary.yml'); + workflowContent = fs.readFileSync(workflowPath, 'utf8'); + }); + + describe('YAML Structure', () => { + test('workflow file exists and is readable', () => { + expect(workflowContent).toBeDefined(); + expect(workflowContent.length).toBeGreaterThan(0); + }); + + test('contains required workflow properties', () => { + expect(workflowContent).toContain('name:'); + expect(workflowContent).toContain('on:'); + expect(workflowContent).toContain('jobs:'); + }); + + test('has proper YAML indentation (spaces, not tabs)', () => { + const lines = workflowContent.split('\n'); + const tabLines = lines.filter(line => line.includes('\t')); + expect(tabLines.length).toBe(0); + }); + + test('does not contain merge conflict markers', () => { + expect(workflowContent).not.toContain('<<<<<<<'); + expect(workflowContent).not.toContain('======='); + expect(workflowContent).not.toContain('>>>>>>>'); + }); + }); + + describe('Security Handling Features', () => { + test('contains security keyword detection instructions', () => { + expect(workflowContent).toContain('ADDITIONAL SECURITY INSTRUCTIONS'); + expect(workflowContent).toContain('[SECURITY]'); + expect(workflowContent).toContain('security vulnerability'); + }); + + test('has security handling step', () => { + expect(workflowContent).toContain('Handle special keywords'); + expect(workflowContent).toContain("steps.final.outputs.response == '[SECURITY]'"); + }); + + test('security step redacts sensitive information', () => { + expect(workflowContent).toContain('Redacted Security Vulnerability'); + expect(workflowContent).toContain('hidden the original content'); + }); + + test('security step adds security label', () => { + expect(workflowContent).toContain('--add-label "security"'); + }); + + test('security step closes and locks issue', () => { + const securitySection = workflowContent.substring( + workflowContent.indexOf('Handle special keywords'), + workflowContent.indexOf('Handle spam keyword') + ); + expect(securitySection).toContain('gh issue close'); + expect(securitySection).toContain('gh issue lock'); + }); + + test('prevents public comment on security issues', () => { + const commentSection = workflowContent.substring( + workflowContent.indexOf('Comment with AI summary') + ); + expect(commentSection).toContain("steps.final.outputs.response != '[SECURITY]'"); + }); + + test('security instructions mention never revealing details', () => { + expect(workflowContent).toContain('Never reveal vulnerability details in public comments'); + }); + + test('provides secure reporting channel', () => { + expect(workflowContent).toContain('security/advisories/new'); + expect(workflowContent).toContain('private reporting channel'); + }); + }); + + describe('Special Keywords', () => { + test('contains all special keyword handlers', () => { + expect(workflowContent).toContain('[SECURITY]'); + expect(workflowContent).toContain('[SPAM]'); + expect(workflowContent).toContain('[CLOSE]'); + expect(workflowContent).toContain('[LOCK]'); + expect(workflowContent).toContain('[LOCKDOWN]'); + }); + + test('has proper conditional checks for keywords', () => { + expect(workflowContent).toContain("== '[SECURITY]'"); + expect(workflowContent).toContain("== '[SPAM]'"); + expect(workflowContent).toContain("== '[CLOSE]'"); + expect(workflowContent).toContain("== '[LOCK]'"); + }); + + test('keyword section has proper ordering', () => { + const securityIndex = workflowContent.indexOf("== '[SECURITY]'"); + const spamIndex = workflowContent.indexOf("== '[SPAM]'"); + const closeIndex = workflowContent.indexOf("== '[CLOSE]'"); + const lockIndex = workflowContent.indexOf("== '[LOCK]'"); + + expect(securityIndex).toBeGreaterThan(0); + expect(spamIndex).toBeGreaterThan(securityIndex); + expect(closeIndex).toBeGreaterThan(spamIndex); + expect(lockIndex).toBeGreaterThan(closeIndex); + }); + }); + + describe('Environment Variables', () => { + test('uses GITHUB_TOKEN for authentication', () => { + expect(workflowContent).toContain('GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}'); + }); + + test('uses ISSUE_NUMBER from event context', () => { + expect(workflowContent).toContain('ISSUE_NUMBER: ${{ github.event.issue.number }}'); + }); + + test('GitHub CLI commands use environment variables', () => { + const securitySection = workflowContent.substring( + workflowContent.indexOf('Handle special keywords') + ); + expect(securitySection).toContain('$ISSUE_NUMBER'); + expect(securitySection).toContain('gh issue'); + }); + }); + + describe('Step Names and Structure', () => { + test('has descriptive step names', () => { + expect(workflowContent).toContain('name: Handle special keywords'); + expect(workflowContent).toContain('name: Handle spam keyword'); + expect(workflowContent).toContain('name: Comment with AI summary'); + }); + + test('uses proper YAML syntax for conditionals', () => { + const conditionalPattern = /if:\s*\$\{\{\s*.+\s*\}\}/g; + const conditionals = workflowContent.match(conditionalPattern); + expect(conditionals).toBeTruthy(); + expect(conditionals.length).toBeGreaterThan(0); + }); + + test('multi-line shell commands are properly formatted', () => { + const runBlocks = workflowContent.split('run: |'); + expect(runBlocks.length).toBeGreaterThan(1); + }); + }); + + describe('Security Best Practices', () => { + test('uses GitHub CLI securely', () => { + expect(workflowContent).toContain('gh issue'); + expect(workflowContent).not.toContain('curl -H "Authorization: Bearer'); + }); + + test('properly quotes shell variables', () => { + const securitySection = workflowContent.substring( + workflowContent.indexOf('Handle special keywords') + ); + expect(securitySection).toMatch(/"\$\w+"/); + }); + }); + + describe('Error Handling and Edge Cases', () => { + test('handles empty or missing responses gracefully', () => { + expect(workflowContent).toContain("!= '[SECURITY]'"); + }); + + test('provides user-friendly messages', () => { + expect(workflowContent).toContain('Thank you for your security report'); + expect(workflowContent).toContain('appreciate your responsible disclosure'); + }); + + test('includes helpful links for users', () => { + expect(workflowContent).toMatch(/\[.*\]\(https:\/\/.+\)/); + }); + }); + + describe('Integration with AI Summary', () => { + test('references AI output from previous steps', () => { + expect(workflowContent).toContain('steps.final.outputs.response'); + }); + + test('has conditional logic based on AI response', () => { + expect(workflowContent).toContain("if: ${{ steps.final.outputs.response == '[SECURITY]' }}"); + }); + }); + + describe('Regression Tests', () => { + test('maintains backward compatibility with existing keywords', () => { + expect(workflowContent).toContain('OTHER SPECIAL KEYWORDS'); + expect(workflowContent).toContain('[CLOSE]'); + expect(workflowContent).toContain('[LOCK]'); + expect(workflowContent).toContain('[LOCKDOWN]'); + }); + + test('preserves spam handling functionality', () => { + expect(workflowContent).toContain('Handle spam keyword'); + }); + + test('preserves normal commenting functionality', () => { + expect(workflowContent).toContain('Comment with AI summary'); + }); + }); + + describe('Documentation and Comments', () => { + test('includes helpful comments for maintainers', () => { + const commentPattern = /#[^\n]+/g; + const comments = workflowContent.match(commentPattern); + expect(comments).toBeTruthy(); + expect(comments.length).toBeGreaterThan(5); + }); + + test('has section dividers for clarity', () => { + expect(workflowContent).toContain('---'); + }); + }); +}); \ No newline at end of file From 1432c7c643292643a0643791c626c6179300397d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 Dec 2025 06:15:38 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=8E=A8=20Auto-fix=20ESLint=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed the following: Triggered by @github-actions[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- eslint-report.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 eslint-report.json diff --git a/eslint-report.json b/eslint-report.json new file mode 100644 index 000000000..e69de29bb From 4bdc6d0f8107062f0f5c28d4c511eb3bf0b632ed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 Dec 2025 14:13:46 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20Auto-fix=20ESLint=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed the following: Triggered by @github-actions[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- eslint-report.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 eslint-report.json diff --git a/eslint-report.json b/eslint-report.json deleted file mode 100644 index e69de29bb..000000000