Skip to content

Commit 9b200d0

Browse files
authored
test: add unit tests for compliance report generation (#2803)
* test: add unit tests for compliance report generation * refactor: remove unused variable * chore: fix testifylint error * refactor: rename package * chore: reorder imports
1 parent 4e1c45f commit 9b200d0

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package compliance
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/types"
15+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
16+
17+
"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
18+
"github.com/aquasecurity/trivy-operator/pkg/ext"
19+
"github.com/aquasecurity/trivy-operator/pkg/operator/etc"
20+
"github.com/aquasecurity/trivy-operator/pkg/trivyoperator"
21+
)
22+
23+
// fakeMgr implements Mgr and records calls.
24+
type fakeMgr struct {
25+
called bool
26+
spec v1alpha1.ReportSpec
27+
err error
28+
}
29+
30+
func (f *fakeMgr) GenerateComplianceReport(_ context.Context, spec v1alpha1.ReportSpec) error {
31+
f.called = true
32+
f.spec = spec
33+
return f.err
34+
}
35+
36+
func TestClusterComplianceReconciler_generateComplianceReport(t *testing.T) {
37+
now := time.Date(2024, 1, 1, 10, 0, 0, 0, time.UTC)
38+
mkReport := func(createdAgo, updatedAgo time.Duration, cron string, format v1alpha1.ReportType) *v1alpha1.ClusterComplianceReport {
39+
return &v1alpha1.ClusterComplianceReport{
40+
TypeMeta: metav1.TypeMeta{Kind: "ClusterComplianceReport"},
41+
ObjectMeta: metav1.ObjectMeta{Name: "nsa", CreationTimestamp: metav1.NewTime(now.Add(-createdAgo))},
42+
Spec: v1alpha1.ReportSpec{
43+
Cron: cron,
44+
ReportFormat: format,
45+
Compliance: v1alpha1.Compliance{ID: "nsa", Title: "nsa"},
46+
},
47+
Status: v1alpha1.ReportStatus{UpdateTimestamp: metav1.NewTime(now.Add(-updatedAgo))},
48+
}
49+
}
50+
51+
type expectation struct {
52+
wantErr bool
53+
mgrCalled bool
54+
requeuePos bool // expect positive RequeueAfter
55+
wantFile bool // expect JSON written to AltReportDir
56+
}
57+
58+
tests := []struct {
59+
name string
60+
setup func() (*ClusterComplianceReportReconciler, types.NamespacedName, *fakeMgr, string)
61+
expect expectation
62+
}{
63+
{
64+
name: "not found is ignored",
65+
setup: func() (*ClusterComplianceReportReconciler, types.NamespacedName, *fakeMgr, string) {
66+
fm := &fakeMgr{}
67+
r := &ClusterComplianceReportReconciler{
68+
Client: fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).Build(),
69+
Mgr: fm,
70+
Clock: ext.NewFixedClock(now),
71+
}
72+
return r, types.NamespacedName{Name: "missing"}, fm, ""
73+
},
74+
expect: expectation{wantErr: false, mgrCalled: false, requeuePos: false, wantFile: false},
75+
},
76+
{
77+
name: "not due requeues",
78+
setup: func() (*ClusterComplianceReportReconciler, types.NamespacedName, *fakeMgr, string) {
79+
cr := mkReport(2*time.Minute, 0, "* * * * *", v1alpha1.ReportSummary) // updated at now
80+
c := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(cr).Build()
81+
fm := &fakeMgr{}
82+
r := &ClusterComplianceReportReconciler{Client: c, Mgr: fm, Clock: ext.NewFixedClock(now)}
83+
return r, types.NamespacedName{Name: "nsa"}, fm, ""
84+
},
85+
expect: expectation{wantErr: false, mgrCalled: false, requeuePos: true, wantFile: false},
86+
},
87+
{
88+
name: "due with alt storage writes file",
89+
setup: func() (*ClusterComplianceReportReconciler, types.NamespacedName, *fakeMgr, string) {
90+
dir := t.TempDir()
91+
cr := mkReport(10*time.Minute, 2*time.Minute, "* * * * *", v1alpha1.ReportSummary)
92+
c := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(cr).Build()
93+
fm := &fakeMgr{}
94+
r := &ClusterComplianceReportReconciler{Client: c, Mgr: fm, Clock: ext.NewFixedClock(now), Config: etc.Config{AltReportStorageEnabled: true, AltReportDir: dir}}
95+
return r, types.NamespacedName{Name: "nsa"}, fm, dir
96+
},
97+
expect: expectation{wantErr: false, mgrCalled: false, requeuePos: false, wantFile: true},
98+
},
99+
{
100+
name: "invoke once calls mgr",
101+
setup: func() (*ClusterComplianceReportReconciler, types.NamespacedName, *fakeMgr, string) {
102+
cr := mkReport(10*time.Minute, 2*time.Minute, "* * * * *", v1alpha1.ReportSummary)
103+
c := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(cr).Build()
104+
fm := &fakeMgr{}
105+
r := &ClusterComplianceReportReconciler{Client: c, Mgr: fm, Clock: ext.NewFixedClock(now), Config: etc.Config{InvokeClusterComplianceOnce: true}}
106+
return r, types.NamespacedName{Name: "nsa"}, fm, ""
107+
},
108+
expect: expectation{wantErr: false, mgrCalled: true, requeuePos: false, wantFile: false},
109+
},
110+
{
111+
name: "invalid cron returns error",
112+
setup: func() (*ClusterComplianceReportReconciler, types.NamespacedName, *fakeMgr, string) {
113+
cr := mkReport(0, 0, "invalid cron", v1alpha1.ReportSummary)
114+
c := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(cr).Build()
115+
fm := &fakeMgr{}
116+
r := &ClusterComplianceReportReconciler{Client: c, Mgr: fm, Clock: ext.NewFixedClock(now)}
117+
return r, types.NamespacedName{Name: "nsa"}, fm, ""
118+
},
119+
expect: expectation{wantErr: true},
120+
},
121+
}
122+
123+
for _, tt := range tests {
124+
t.Run(tt.name, func(t *testing.T) {
125+
r, nn, fm, dir := tt.setup()
126+
res, err := r.generateComplianceReport(context.TODO(), nn)
127+
128+
if tt.expect.wantErr {
129+
require.Error(t, err)
130+
return
131+
}
132+
require.NoError(t, err)
133+
134+
assert.Equal(t, tt.expect.mgrCalled, fm.called)
135+
if tt.expect.requeuePos {
136+
assert.Positive(t, int64(res.RequeueAfter))
137+
}
138+
139+
if tt.expect.wantFile {
140+
outDir := filepath.Join(dir, "cluster_compliance_report")
141+
entries, readErr := os.ReadDir(outDir)
142+
require.NoError(t, readErr)
143+
require.Len(t, entries, 1)
144+
raw, rErr := os.ReadFile(filepath.Join(outDir, entries[0].Name()))
145+
require.NoError(t, rErr)
146+
var decoded v1alpha1.ClusterComplianceReport
147+
require.NoError(t, json.Unmarshal(raw, &decoded))
148+
require.Equal(t, "nsa", decoded.Name)
149+
}
150+
})
151+
}
152+
}

0 commit comments

Comments
 (0)