Skip to content

Commit 7fff1b2

Browse files
committed
test: add comprehensive unit tests for internal/attestation package
This commit adds extensive unit test coverage for the attestation package, including both success and failure scenarios. The changes include: Add new test functions for provenance operations: - TestProvenance_Type: Tests type getter returning in-toto statement type - TestProvenance_Statement: Tests statement data retrieval with various data states - TestProvenance_Signatures: Tests signature collection handling - TestProvenance_Subject: Tests subject retrieval from attestation statements - TestProvenance_MarshalJSON: Tests JSON marshaling with different signature configurations - TestSLSAProvenance_Subject: Tests SLSA provenance subject retrieval with realistic container data These changes added 6 new test functions. Assisted-by: Claude Code Ref: https://issues.redhat.com/browse/EC-1496
1 parent 680c321 commit 7fff1b2

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

internal/attestation/attestation_test.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ package attestation
2020

2121
import (
2222
"crypto/x509"
23+
"encoding/json"
2324
"fmt"
2425
"testing"
2526

2627
"github.com/gkampitakis/go-snaps/snaps"
2728
"github.com/google/go-containerregistry/pkg/v1/types"
29+
"github.com/in-toto/in-toto-golang/in_toto"
2830
ct "github.com/sigstore/cosign/v2/pkg/types"
2931
"github.com/stretchr/testify/assert"
3032
"github.com/stretchr/testify/mock"
@@ -125,3 +127,248 @@ func TestProvenanceFromSignature(t *testing.T) {
125127
})
126128
}
127129
}
130+
131+
func TestProvenance_Type(t *testing.T) {
132+
tests := []struct {
133+
name string
134+
expected string
135+
}{
136+
{
137+
name: "returns correct in-toto statement type",
138+
expected: "https://in-toto.io/Statement/v0.1",
139+
},
140+
}
141+
142+
for _, tt := range tests {
143+
t.Run(tt.name, func(t *testing.T) {
144+
p := provenance{}
145+
result := p.Type()
146+
assert.Equal(t, tt.expected, result)
147+
})
148+
}
149+
}
150+
151+
func TestProvenance_Statement(t *testing.T) {
152+
tests := []struct {
153+
name string
154+
data []byte
155+
expected []byte
156+
}{
157+
{
158+
name: "returns stored data correctly",
159+
data: []byte(`{"test": "data"}`),
160+
expected: []byte(`{"test": "data"}`),
161+
},
162+
{
163+
name: "returns empty data when nil",
164+
data: nil,
165+
expected: nil,
166+
},
167+
{
168+
name: "returns empty data when empty slice",
169+
data: []byte{},
170+
expected: []byte{},
171+
},
172+
}
173+
174+
for _, tt := range tests {
175+
t.Run(tt.name, func(t *testing.T) {
176+
p := provenance{data: tt.data}
177+
result := p.Statement()
178+
assert.Equal(t, tt.expected, result)
179+
})
180+
}
181+
}
182+
183+
func TestProvenance_Signatures(t *testing.T) {
184+
mockSig1 := signature.EntitySignature{
185+
KeyID: "key1",
186+
Signature: "sig1",
187+
}
188+
mockSig2 := signature.EntitySignature{
189+
KeyID: "key2",
190+
Signature: "sig2",
191+
}
192+
193+
tests := []struct {
194+
name string
195+
signatures []signature.EntitySignature
196+
expected []signature.EntitySignature
197+
}{
198+
{
199+
name: "returns single signature",
200+
signatures: []signature.EntitySignature{mockSig1},
201+
expected: []signature.EntitySignature{mockSig1},
202+
},
203+
{
204+
name: "returns multiple signatures",
205+
signatures: []signature.EntitySignature{mockSig1, mockSig2},
206+
expected: []signature.EntitySignature{mockSig1, mockSig2},
207+
},
208+
{
209+
name: "returns empty slice when no signatures",
210+
signatures: []signature.EntitySignature{},
211+
expected: []signature.EntitySignature{},
212+
},
213+
}
214+
215+
for _, tt := range tests {
216+
t.Run(tt.name, func(t *testing.T) {
217+
p := provenance{signatures: tt.signatures}
218+
result := p.Signatures()
219+
assert.Equal(t, tt.expected, result)
220+
})
221+
}
222+
}
223+
224+
func TestProvenance_Subject(t *testing.T) {
225+
mockSubject1 := in_toto.Subject{
226+
Name: "subject1",
227+
Digest: map[string]string{
228+
"sha256": "digest1",
229+
},
230+
}
231+
mockSubject2 := in_toto.Subject{
232+
Name: "subject2",
233+
Digest: map[string]string{
234+
"sha256": "digest2",
235+
},
236+
}
237+
238+
tests := []struct {
239+
name string
240+
statement in_toto.Statement
241+
expected []in_toto.Subject
242+
}{
243+
{
244+
name: "returns single subject",
245+
statement: in_toto.Statement{
246+
StatementHeader: in_toto.StatementHeader{
247+
Subject: []in_toto.Subject{mockSubject1},
248+
},
249+
},
250+
expected: []in_toto.Subject{mockSubject1},
251+
},
252+
{
253+
name: "returns multiple subjects",
254+
statement: in_toto.Statement{
255+
StatementHeader: in_toto.StatementHeader{
256+
Subject: []in_toto.Subject{mockSubject1, mockSubject2},
257+
},
258+
},
259+
expected: []in_toto.Subject{mockSubject1, mockSubject2},
260+
},
261+
{
262+
name: "returns empty slice when no subjects",
263+
statement: in_toto.Statement{
264+
StatementHeader: in_toto.StatementHeader{
265+
Subject: []in_toto.Subject{},
266+
},
267+
},
268+
expected: []in_toto.Subject{},
269+
},
270+
}
271+
272+
for _, tt := range tests {
273+
t.Run(tt.name, func(t *testing.T) {
274+
p := provenance{statement: tt.statement}
275+
result := p.Subject()
276+
assert.Equal(t, tt.expected, result)
277+
})
278+
}
279+
}
280+
281+
func TestProvenance_MarshalJSON(t *testing.T) {
282+
mockSig1 := signature.EntitySignature{
283+
KeyID: "key1",
284+
Signature: "sig1",
285+
}
286+
mockSig2 := signature.EntitySignature{
287+
KeyID: "key2",
288+
Signature: "sig2",
289+
}
290+
291+
tests := []struct {
292+
name string
293+
provenance provenance
294+
expectedErr bool
295+
validate func(*testing.T, []byte)
296+
}{
297+
{
298+
name: "marshals successfully with single signature",
299+
provenance: provenance{
300+
statement: in_toto.Statement{
301+
StatementHeader: in_toto.StatementHeader{
302+
PredicateType: "https://example.com/predicate/v1",
303+
},
304+
},
305+
signatures: []signature.EntitySignature{mockSig1},
306+
},
307+
expectedErr: false,
308+
validate: func(t *testing.T, data []byte) {
309+
var result map[string]interface{}
310+
err := json.Unmarshal(data, &result)
311+
assert.NoError(t, err)
312+
assert.Equal(t, "https://in-toto.io/Statement/v0.1", result["type"])
313+
assert.Equal(t, "https://example.com/predicate/v1", result["predicateType"])
314+
assert.Len(t, result["signatures"], 1)
315+
},
316+
},
317+
{
318+
name: "marshals successfully with multiple signatures",
319+
provenance: provenance{
320+
statement: in_toto.Statement{
321+
StatementHeader: in_toto.StatementHeader{
322+
PredicateType: "https://example.com/predicate/v2",
323+
},
324+
},
325+
signatures: []signature.EntitySignature{mockSig1, mockSig2},
326+
},
327+
expectedErr: false,
328+
validate: func(t *testing.T, data []byte) {
329+
var result map[string]interface{}
330+
err := json.Unmarshal(data, &result)
331+
assert.NoError(t, err)
332+
assert.Equal(t, "https://in-toto.io/Statement/v0.1", result["type"])
333+
assert.Equal(t, "https://example.com/predicate/v2", result["predicateType"])
334+
assert.Len(t, result["signatures"], 2)
335+
},
336+
},
337+
{
338+
name: "marshals successfully with empty signatures",
339+
provenance: provenance{
340+
statement: in_toto.Statement{
341+
StatementHeader: in_toto.StatementHeader{
342+
PredicateType: "https://example.com/predicate/v3",
343+
},
344+
},
345+
signatures: []signature.EntitySignature{},
346+
},
347+
expectedErr: false,
348+
validate: func(t *testing.T, data []byte) {
349+
var result map[string]interface{}
350+
err := json.Unmarshal(data, &result)
351+
assert.NoError(t, err)
352+
assert.Equal(t, "https://in-toto.io/Statement/v0.1", result["type"])
353+
assert.Equal(t, "https://example.com/predicate/v3", result["predicateType"])
354+
assert.Len(t, result["signatures"], 0)
355+
},
356+
},
357+
}
358+
359+
for _, tt := range tests {
360+
t.Run(tt.name, func(t *testing.T) {
361+
data, err := tt.provenance.MarshalJSON()
362+
363+
if tt.expectedErr {
364+
assert.Error(t, err)
365+
return
366+
}
367+
368+
assert.NoError(t, err)
369+
if tt.validate != nil {
370+
tt.validate(t, data)
371+
}
372+
})
373+
}
374+
}

internal/attestation/slsa_provenance_02_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/gkampitakis/go-snaps/snaps"
3232
v1 "github.com/google/go-containerregistry/pkg/v1"
3333
"github.com/google/go-containerregistry/pkg/v1/types"
34+
"github.com/in-toto/in-toto-golang/in_toto"
3435
"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
3536
ct "github.com/sigstore/cosign/v2/pkg/types"
3637
"github.com/stretchr/testify/assert"
@@ -365,3 +366,78 @@ func encode(payload string) string {
365366
func buffy(data string) io.ReadCloser {
366367
return io.NopCloser(bytes.NewBufferString(data))
367368
}
369+
370+
func TestSLSAProvenance_Subject(t *testing.T) {
371+
mockSubject1 := in_toto.Subject{
372+
Name: "registry.io/example/image@sha256:abc123",
373+
Digest: map[string]string{
374+
"sha256": "abc123def456",
375+
},
376+
}
377+
mockSubject2 := in_toto.Subject{
378+
Name: "registry.io/example/artifact@sha256:def456",
379+
Digest: map[string]string{
380+
"sha256": "def456abc123",
381+
"sha512": "fea789bcd012",
382+
},
383+
}
384+
385+
tests := []struct {
386+
name string
387+
statement in_toto.ProvenanceStatementSLSA02
388+
expected []in_toto.Subject
389+
wantPanic bool
390+
}{
391+
{
392+
name: "returns single subject successfully",
393+
statement: in_toto.ProvenanceStatementSLSA02{
394+
StatementHeader: in_toto.StatementHeader{
395+
Subject: []in_toto.Subject{mockSubject1},
396+
},
397+
},
398+
expected: []in_toto.Subject{mockSubject1},
399+
},
400+
{
401+
name: "returns multiple subjects successfully",
402+
statement: in_toto.ProvenanceStatementSLSA02{
403+
StatementHeader: in_toto.StatementHeader{
404+
Subject: []in_toto.Subject{mockSubject1, mockSubject2},
405+
},
406+
},
407+
expected: []in_toto.Subject{mockSubject1, mockSubject2},
408+
},
409+
{
410+
name: "returns empty slice when no subjects",
411+
statement: in_toto.ProvenanceStatementSLSA02{
412+
StatementHeader: in_toto.StatementHeader{
413+
Subject: []in_toto.Subject{},
414+
},
415+
},
416+
expected: []in_toto.Subject{},
417+
},
418+
}
419+
420+
for _, tt := range tests {
421+
t.Run(tt.name, func(t *testing.T) {
422+
if tt.wantPanic {
423+
defer func() {
424+
if r := recover(); r == nil {
425+
t.Errorf("expected panic but none occurred")
426+
}
427+
}()
428+
}
429+
430+
slsa := slsaProvenance{statement: tt.statement}
431+
result := slsa.Subject()
432+
433+
if !tt.wantPanic {
434+
assert.Equal(t, tt.expected, result)
435+
// Verify that the returned slice is independent of the original
436+
if len(result) > 0 && len(tt.expected) > 0 {
437+
assert.Equal(t, tt.expected[0].Name, result[0].Name)
438+
assert.Equal(t, tt.expected[0].Digest, result[0].Digest)
439+
}
440+
}
441+
})
442+
}
443+
}

0 commit comments

Comments
 (0)