Skip to content

Commit e0a13b2

Browse files
test: add comprehensive tests for image-tag module
Cover all previously-uncovered lines in src/image-tag.ts: - Empty/whitespace tag validation (lines 18, 24) - Empty digest entry skip (line 33) - Malformed digest entry errors (line 38) - Invalid digest key validation (line 47) - Invalid runtime image name in buildRuntimeImageRef (line 70) Also adds positive tests for valid inputs, multiple digests, whitespace trimming, and all supported image keys. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 836144a commit e0a13b2

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

src/image-tag.test.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { parseImageTag, buildRuntimeImageRef, IMAGE_DIGEST_KEYS } from './image-tag';
2+
3+
const VALID_DIGEST = 'sha256:' + 'a'.repeat(64);
4+
5+
describe('parseImageTag', () => {
6+
describe('when given valid input', () => {
7+
it('should parse legacy tag format', () => {
8+
const result = parseImageTag('0.25.18');
9+
expect(result.tag).toBe('0.25.18');
10+
expect(result.digests).toEqual({});
11+
});
12+
13+
it('should parse tag with single digest', () => {
14+
const result = parseImageTag(`0.25.18,squid=${VALID_DIGEST}`);
15+
expect(result.tag).toBe('0.25.18');
16+
expect(result.digests.squid).toBe(VALID_DIGEST);
17+
});
18+
19+
it('should parse tag with multiple digests', () => {
20+
const agentDigest = 'sha256:' + 'b'.repeat(64);
21+
const result = parseImageTag(`0.25.18,squid=${VALID_DIGEST},agent=${agentDigest}`);
22+
expect(result.tag).toBe('0.25.18');
23+
expect(result.digests.squid).toBe(VALID_DIGEST);
24+
expect(result.digests.agent).toBe(agentDigest);
25+
});
26+
27+
it('should handle all supported digest keys', () => {
28+
const entries = IMAGE_DIGEST_KEYS.map((k, i) => `${k}=sha256:${'a'.repeat(63)}${i}`).join(
29+
','
30+
);
31+
const result = parseImageTag(`latest,${entries}`);
32+
expect(result.tag).toBe('latest');
33+
for (const key of IMAGE_DIGEST_KEYS) {
34+
expect(result.digests[key]).toBeDefined();
35+
}
36+
});
37+
38+
it('should trim whitespace around tag and entries', () => {
39+
const result = parseImageTag(` v1.0 , squid = ${VALID_DIGEST} `);
40+
expect(result.tag).toBe('v1.0');
41+
expect(result.digests.squid).toBe(VALID_DIGEST);
42+
});
43+
44+
it('should skip empty digest entries (trailing comma)', () => {
45+
const result = parseImageTag(`0.25.18,squid=${VALID_DIGEST},`);
46+
expect(result.tag).toBe('0.25.18');
47+
expect(result.digests.squid).toBe(VALID_DIGEST);
48+
});
49+
});
50+
51+
describe('when given invalid input', () => {
52+
it('should throw when tag is empty string', () => {
53+
expect(() => parseImageTag('')).toThrow('tag cannot be empty');
54+
});
55+
56+
it('should throw when tag is whitespace only', () => {
57+
expect(() => parseImageTag(' ')).toThrow('tag cannot be empty');
58+
});
59+
60+
it('should throw when tag portion is empty after split (leading comma)', () => {
61+
expect(() => parseImageTag(`,squid=${VALID_DIGEST}`)).toThrow('tag cannot be empty');
62+
});
63+
64+
it('should throw for digest entry without equals sign', () => {
65+
expect(() => parseImageTag('v1.0,squid')).toThrow('Expected format');
66+
});
67+
68+
it('should throw for digest entry with equals at position 0 (empty key)', () => {
69+
expect(() => parseImageTag(`v1.0,=${VALID_DIGEST}`)).toThrow('Expected format');
70+
});
71+
72+
it('should throw for digest entry with equals at last position (empty value)', () => {
73+
expect(() => parseImageTag('v1.0,squid=')).toThrow('Expected format');
74+
});
75+
76+
it('should throw for unrecognized digest key', () => {
77+
expect(() => parseImageTag(`v1.0,unknown=${VALID_DIGEST}`)).toThrow(
78+
'Invalid --image-tag digest key "unknown"'
79+
);
80+
});
81+
82+
it('should throw for digest that is not sha256 format', () => {
83+
expect(() => parseImageTag('v1.0,squid=md5:abc')).toThrow(
84+
'Expected lowercase sha256:<64-hex>'
85+
);
86+
});
87+
88+
it('should throw for sha256 digest with wrong length', () => {
89+
expect(() => parseImageTag('v1.0,squid=sha256:abc123')).toThrow(
90+
'Expected lowercase sha256:<64-hex>'
91+
);
92+
});
93+
94+
it('should throw for sha256 digest with uppercase hex', () => {
95+
expect(() => parseImageTag(`v1.0,squid=sha256:${'A'.repeat(64)}`)).toThrow(
96+
'Expected lowercase sha256:<64-hex>'
97+
);
98+
});
99+
100+
it('should throw for sha256 digest with non-hex characters', () => {
101+
expect(() => parseImageTag(`v1.0,squid=sha256:${'g'.repeat(64)}`)).toThrow(
102+
'Expected lowercase sha256:<64-hex>'
103+
);
104+
});
105+
});
106+
});
107+
108+
describe('buildRuntimeImageRef', () => {
109+
describe('when given valid input', () => {
110+
it('should build ref without digest when none provided', () => {
111+
const parsed = parseImageTag('0.25.18');
112+
const ref = buildRuntimeImageRef('ghcr.io/github/gh-aw-firewall', 'squid', parsed);
113+
expect(ref).toBe('ghcr.io/github/gh-aw-firewall/squid:0.25.18');
114+
});
115+
116+
it('should build ref with digest when provided', () => {
117+
const parsed = parseImageTag(`0.25.18,squid=${VALID_DIGEST}`);
118+
const ref = buildRuntimeImageRef('ghcr.io/github/gh-aw-firewall', 'squid', parsed);
119+
expect(ref).toBe(`ghcr.io/github/gh-aw-firewall/squid:0.25.18@${VALID_DIGEST}`);
120+
});
121+
122+
it('should build ref for agent image', () => {
123+
const agentDigest = 'sha256:' + 'c'.repeat(64);
124+
const parsed = parseImageTag(`latest,agent=${agentDigest}`);
125+
const ref = buildRuntimeImageRef('registry.example.com/myorg', 'agent', parsed);
126+
expect(ref).toBe(`registry.example.com/myorg/agent:latest@${agentDigest}`);
127+
});
128+
129+
it('should build ref without digest when image has no matching digest', () => {
130+
const parsed = parseImageTag(`0.25.18,squid=${VALID_DIGEST}`);
131+
const ref = buildRuntimeImageRef('ghcr.io/github/gh-aw-firewall', 'agent', parsed);
132+
expect(ref).toBe('ghcr.io/github/gh-aw-firewall/agent:0.25.18');
133+
});
134+
});
135+
136+
describe('when given invalid image name', () => {
137+
it('should throw for unknown image name', () => {
138+
const parsed = parseImageTag('0.25.18');
139+
expect(() =>
140+
buildRuntimeImageRef('ghcr.io/github/gh-aw-firewall', 'unknown-image', parsed)
141+
).toThrow('Invalid runtime image name "unknown-image"');
142+
});
143+
144+
it('should mention supported names in error', () => {
145+
const parsed = parseImageTag('0.25.18');
146+
expect(() =>
147+
buildRuntimeImageRef('ghcr.io/github/gh-aw-firewall', 'bad', parsed)
148+
).toThrow(IMAGE_DIGEST_KEYS.join(', '));
149+
});
150+
});
151+
});

0 commit comments

Comments
 (0)