github-commit-image.test.mjs
192 lines 6.0 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 /**
2 * Tests for lib/github-commit-image.mjs — repo URL parsing, file validation, magic bytes.
3 */
4 import { describe, it } from 'node:test';
5 import assert from 'node:assert';
6 import {
7 parseGitHubRepoUrl,
8 validateImageExtension,
9 validateMagicBytes,
10 ALLOWED_EXTENSIONS,
11 } from '../lib/github-commit-image.mjs';
12
13 describe('parseGitHubRepoUrl', () => {
14 it('parses https URL with .git suffix', () => {
15 const result = parseGitHubRepoUrl('https://github.com/user/repo.git');
16 assert.deepStrictEqual(result, { owner: 'user', repo: 'repo' });
17 });
18
19 it('parses https URL without .git suffix', () => {
20 const result = parseGitHubRepoUrl('https://github.com/user/repo');
21 assert.deepStrictEqual(result, { owner: 'user', repo: 'repo' });
22 });
23
24 it('parses URL with trailing slash', () => {
25 const result = parseGitHubRepoUrl('https://github.com/user/repo/');
26 assert.deepStrictEqual(result, { owner: 'user', repo: 'repo' });
27 });
28
29 it('handles mixed case', () => {
30 const result = parseGitHubRepoUrl('https://GitHub.com/Owner/Repo.git');
31 assert.strictEqual(result.owner, 'Owner');
32 assert.strictEqual(result.repo, 'Repo');
33 });
34
35 it('throws for non-GitHub URL', () => {
36 assert.throws(
37 () => parseGitHubRepoUrl('https://gitlab.com/user/repo'),
38 /Cannot parse/,
39 );
40 });
41
42 it('throws for empty string', () => {
43 assert.throws(() => parseGitHubRepoUrl(''), /required/);
44 });
45
46 it('throws for null', () => {
47 assert.throws(() => parseGitHubRepoUrl(null), /required/);
48 });
49
50 it('parses SSH-style URL', () => {
51 const result = parseGitHubRepoUrl('[email protected]:user/repo.git');
52 assert.deepStrictEqual(result, { owner: 'user', repo: 'repo' });
53 });
54
55 it('parses short owner/repo slug (format stored by the bridge)', () => {
56 const result = parseGitHubRepoUrl('aaronrene/knowtation-vault-hosted');
57 assert.deepStrictEqual(result, { owner: 'aaronrene', repo: 'knowtation-vault-hosted' });
58 });
59
60 it('parses short owner/repo.git slug', () => {
61 const result = parseGitHubRepoUrl('user/my-vault.git');
62 assert.deepStrictEqual(result, { owner: 'user', repo: 'my-vault' });
63 });
64 });
65
66 describe('validateImageExtension', () => {
67 it('accepts .jpg', () => {
68 assert.strictEqual(validateImageExtension('photo.jpg'), 'jpg');
69 });
70
71 it('accepts .jpeg', () => {
72 assert.strictEqual(validateImageExtension('photo.jpeg'), 'jpeg');
73 });
74
75 it('accepts .png', () => {
76 assert.strictEqual(validateImageExtension('img.png'), 'png');
77 });
78
79 it('accepts .gif', () => {
80 assert.strictEqual(validateImageExtension('anim.gif'), 'gif');
81 });
82
83 it('accepts .webp', () => {
84 assert.strictEqual(validateImageExtension('photo.webp'), 'webp');
85 });
86
87 it('rejects .exe', () => {
88 assert.throws(() => validateImageExtension('malware.exe'), /not allowed/);
89 });
90
91 it('rejects .html', () => {
92 assert.throws(() => validateImageExtension('page.html'), /not allowed/);
93 });
94
95 it('rejects .svg', () => {
96 assert.throws(() => validateImageExtension('icon.svg'), /not allowed/);
97 });
98
99 it('rejects .pdf', () => {
100 assert.throws(() => validateImageExtension('doc.pdf'), /not allowed/);
101 });
102
103 it('throws for empty filename', () => {
104 assert.throws(() => validateImageExtension(''), /required/);
105 });
106
107 it('handles uppercase extensions', () => {
108 assert.strictEqual(validateImageExtension('PHOTO.JPG'), 'jpg');
109 });
110 });
111
112 describe('validateMagicBytes', () => {
113 it('validates JPEG magic bytes', () => {
114 const buf = Buffer.from([0xFF, 0xD8, 0xFF, 0xE0, 0x00]);
115 assert.strictEqual(validateMagicBytes(buf, 'jpg'), true);
116 assert.strictEqual(validateMagicBytes(buf, 'jpeg'), true);
117 });
118
119 it('validates PNG magic bytes', () => {
120 const buf = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
121 assert.strictEqual(validateMagicBytes(buf, 'png'), true);
122 });
123
124 it('validates GIF87a magic bytes', () => {
125 const buf = Buffer.from([0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x00, 0x00]);
126 assert.strictEqual(validateMagicBytes(buf, 'gif'), true);
127 });
128
129 it('validates GIF89a magic bytes', () => {
130 const buf = Buffer.from([0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x00]);
131 assert.strictEqual(validateMagicBytes(buf, 'gif'), true);
132 });
133
134 it('validates WebP magic bytes (RIFF...WEBP)', () => {
135 const buf = Buffer.from([
136 0x52, 0x49, 0x46, 0x46,
137 0x00, 0x00, 0x00, 0x00,
138 0x57, 0x45, 0x42, 0x50,
139 ]);
140 assert.strictEqual(validateMagicBytes(buf, 'webp'), true);
141 });
142
143 it('rejects JPEG extension with PNG magic bytes', () => {
144 const buf = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
145 assert.strictEqual(validateMagicBytes(buf, 'jpg'), false);
146 });
147
148 it('rejects PNG extension with JPEG magic bytes', () => {
149 const buf = Buffer.from([0xFF, 0xD8, 0xFF, 0xE0, 0x00]);
150 assert.strictEqual(validateMagicBytes(buf, 'png'), false);
151 });
152
153 it('rejects empty buffer', () => {
154 assert.strictEqual(validateMagicBytes(Buffer.alloc(0), 'jpg'), false);
155 });
156
157 it('rejects null buffer', () => {
158 assert.strictEqual(validateMagicBytes(null, 'jpg'), false);
159 });
160
161 it('rejects buffer too short', () => {
162 const buf = Buffer.from([0xFF, 0xD8]);
163 assert.strictEqual(validateMagicBytes(buf, 'jpg'), false);
164 });
165
166 it('rejects unknown extension', () => {
167 const buf = Buffer.from([0xFF, 0xD8, 0xFF, 0xE0]);
168 assert.strictEqual(validateMagicBytes(buf, 'bmp'), false);
169 });
170
171 it('rejects WebP without WEBP marker', () => {
172 const buf = Buffer.from([
173 0x52, 0x49, 0x46, 0x46,
174 0x00, 0x00, 0x00, 0x00,
175 0x00, 0x00, 0x00, 0x00,
176 ]);
177 assert.strictEqual(validateMagicBytes(buf, 'webp'), false);
178 });
179 });
180
181 describe('ALLOWED_EXTENSIONS', () => {
182 it('contains expected set', () => {
183 assert.ok(ALLOWED_EXTENSIONS.has('jpg'));
184 assert.ok(ALLOWED_EXTENSIONS.has('jpeg'));
185 assert.ok(ALLOWED_EXTENSIONS.has('png'));
186 assert.ok(ALLOWED_EXTENSIONS.has('gif'));
187 assert.ok(ALLOWED_EXTENSIONS.has('webp'));
188 assert.ok(!ALLOWED_EXTENSIONS.has('svg'));
189 assert.ok(!ALLOWED_EXTENSIONS.has('bmp'));
190 assert.ok(!ALLOWED_EXTENSIONS.has('tiff'));
191 });
192 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago