Skip to content

Commit 3d090d1

Browse files
author
Chris Vermeulen
authored
Add integration tests and broken example (#197)
* Add integration tests and broken example * Update test name to run correctly * Add missing build tag * Fix sub group duplicates * Fill out test * Add broken test for controls * Implement fix for controls * Ensure to not query across catalogs for controls also
1 parent d81c5e6 commit 3d090d1

File tree

7 files changed

+537
-215
lines changed

7 files changed

+537
-215
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,39 @@ jobs:
116116
- name: Run Unit Tests
117117
run: |
118118
make test
119+
120+
integration-tests:
121+
runs-on: ubuntu-latest
122+
steps:
123+
- name: Checkout
124+
uses: actions/checkout@v3
125+
126+
- name: Fetch History
127+
run: git fetch --prune --unshallow
128+
129+
- name: Setup Go
130+
uses: actions/setup-go@v4
131+
with:
132+
go-version-file: "go.mod"
133+
134+
- name: Find the Go Cache
135+
id: go
136+
run: |
137+
echo "::set-output name=build-cache::$(go env GOCACHE)"
138+
echo "::set-output name=mod-cache::$(go env GOMODCACHE)"
139+
140+
- name: Cache the Go Build Cache
141+
uses: actions/cache@v3
142+
with:
143+
path: ${{ steps.go.outputs.build-cache }}
144+
key: ${{ runner.os }}-build-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
145+
146+
- name: Cache Go Dependencies
147+
uses: actions/cache@v3
148+
with:
149+
path: ${{ steps.go.outputs.mod-cache }}
150+
key: ${{ runner.os }}-mod-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
151+
152+
- name: Run Unit Tests
153+
run: |
154+
make test-integration

internal/api/handler/oscal/catalogs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ func (h *CatalogHandler) GetGroupSubGroups(ctx echo.Context) error {
333333
groupID := ctx.Param("group")
334334
var group relational.Group
335335
if err := h.db.
336-
Preload("Groups").
336+
Preload("Groups", "catalog_id = ?", id).
337337
Where("id = ? AND catalog_id = ?", groupID, id).
338338
First(&group).Error; err != nil {
339339
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -372,7 +372,7 @@ func (h *CatalogHandler) GetGroupControls(ctx echo.Context) error {
372372
groupID := ctx.Param("group")
373373
var group relational.Group
374374
if err := h.db.
375-
Preload("Controls").
375+
Preload("Controls", "catalog_id = ?", id).
376376
Where("id = ? AND catalog_id = ?", groupID, id).
377377
First(&group).Error; err != nil {
378378
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -674,7 +674,7 @@ func (h *CatalogHandler) GetControlSubControls(ctx echo.Context) error {
674674
controlID := ctx.Param("control")
675675
var control relational.Control
676676
if err := h.db.
677-
Preload("Controls").
677+
Preload("Controls", "catalog_id = ?", id).
678678
Where("id = ? AND catalog_id = ?", controlID, id).
679679
First(&control).Error; err != nil {
680680
if errors.Is(err, gorm.ErrRecordNotFound) {
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
//go:build integration
2+
3+
package oscal
4+
5+
import (
6+
"bytes"
7+
"context"
8+
"encoding/json"
9+
"github.com/compliance-framework/configuration-service/internal/api"
10+
"github.com/compliance-framework/configuration-service/internal/api/handler"
11+
"github.com/compliance-framework/configuration-service/internal/tests"
12+
oscaltypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
13+
"github.com/labstack/echo/v4"
14+
"github.com/stretchr/testify/assert"
15+
"go.uber.org/zap"
16+
"net/http"
17+
"net/http/httptest"
18+
"testing"
19+
20+
"github.com/stretchr/testify/suite"
21+
)
22+
23+
func TestOscalCatalogApi(t *testing.T) {
24+
suite.Run(t, new(CatalogApiIntegrationSuite))
25+
}
26+
27+
type CatalogApiIntegrationSuite struct {
28+
tests.IntegrationTestSuite
29+
}
30+
31+
// TestDuplicateCatalogGroupID ensures that when multiple catalogs have group children with the same ID,
32+
// their children endpoints only returned the relevant groups.
33+
// This is to prevent a future regression where searching for child groups in a catalog, would return all the groups
34+
// with a matching ID, rather than only the ones which belong to a catalog.
35+
func (suite *CatalogApiIntegrationSuite) TestDuplicateCatalogGroupID() {
36+
logger, _ := zap.NewDevelopment()
37+
38+
err := suite.Migrator.Refresh()
39+
suite.Require().NoError(err)
40+
41+
server := api.NewServer(context.Background(), logger.Sugar())
42+
RegisterHandlers(server, logger.Sugar(), suite.DB)
43+
44+
// Create two catalogs with the same group ID structure
45+
catalogs := []oscaltypes.Catalog{
46+
{
47+
UUID: "D20DB907-B87D-4D12-8760-D36FDB7A1B31",
48+
Metadata: oscaltypes.Metadata{
49+
Title: "Catalog 1",
50+
},
51+
Groups: &[]oscaltypes.Group{
52+
{
53+
ID: "G-1",
54+
Title: "Group 1",
55+
Groups: &[]oscaltypes.Group{
56+
{
57+
ID: "G-1.1",
58+
Title: "Group 1.1",
59+
},
60+
},
61+
},
62+
},
63+
},
64+
{
65+
UUID: "D20DB907-B87D-4D12-8760-D36FDB7A1B32",
66+
Metadata: oscaltypes.Metadata{
67+
Title: "Catalog 2",
68+
},
69+
Groups: &[]oscaltypes.Group{
70+
{
71+
ID: "G-1",
72+
Title: "Group 2",
73+
Groups: &[]oscaltypes.Group{
74+
{
75+
ID: "G-1.1",
76+
Title: "Group 2.1",
77+
},
78+
},
79+
},
80+
},
81+
},
82+
}
83+
for _, catalog := range catalogs {
84+
rec := httptest.NewRecorder()
85+
reqBody, _ := json.Marshal(catalog)
86+
req := httptest.NewRequest(http.MethodPost, "/api/oscal/catalogs", bytes.NewReader(reqBody))
87+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
88+
server.E().ServeHTTP(rec, req)
89+
assert.Equal(suite.T(), http.StatusCreated, rec.Code)
90+
response := &handler.GenericDataResponse[oscaltypes.Catalog]{}
91+
err = json.Unmarshal(rec.Body.Bytes(), response)
92+
suite.Require().NoError(err)
93+
}
94+
95+
// Now if we call to check the children for each catalogs' first group, we should only see 1 item
96+
97+
// The first catalog's group should have the Title Group 1
98+
rec := httptest.NewRecorder()
99+
req := httptest.NewRequest(http.MethodGet, "/api/oscal/catalogs/D20DB907-B87D-4D12-8760-D36FDB7A1B31/groups/G-1/groups", bytes.NewReader([]byte{}))
100+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
101+
server.E().ServeHTTP(rec, req)
102+
assert.Equal(suite.T(), http.StatusOK, rec.Code)
103+
response := &handler.GenericDataListResponse[oscaltypes.Group]{}
104+
err = json.Unmarshal(rec.Body.Bytes(), response)
105+
suite.Require().NoError(err)
106+
suite.Len(response.Data, 1)
107+
suite.Equal(response.Data[0].Title, "Group 1.1")
108+
109+
// The second catalog's group should have the Title Group 1
110+
rec = httptest.NewRecorder()
111+
req = httptest.NewRequest(http.MethodGet, "/api/oscal/catalogs/D20DB907-B87D-4D12-8760-D36FDB7A1B32/groups/G-1/groups", bytes.NewReader([]byte{}))
112+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
113+
server.E().ServeHTTP(rec, req)
114+
assert.Equal(suite.T(), http.StatusOK, rec.Code)
115+
response = &handler.GenericDataListResponse[oscaltypes.Group]{}
116+
err = json.Unmarshal(rec.Body.Bytes(), response)
117+
suite.Require().NoError(err)
118+
suite.Len(response.Data, 1)
119+
suite.Equal(response.Data[0].Title, "Group 2.1")
120+
}
121+
122+
// TestDuplicateCatalogControlID ensures that when multiple catalogs have control children with the same ID,
123+
// their children endpoints only returned the relevant controls.
124+
// This is to prevent a future regression where searching for child controls in a catalog, would return all the controls
125+
// with a matching ID, rather than only the ones which belong to a catalog.
126+
func (suite *CatalogApiIntegrationSuite) TestDuplicateCatalogControlID() {
127+
logger, _ := zap.NewDevelopment()
128+
129+
err := suite.Migrator.Refresh()
130+
suite.Require().NoError(err)
131+
132+
server := api.NewServer(context.Background(), logger.Sugar())
133+
RegisterHandlers(server, logger.Sugar(), suite.DB)
134+
135+
// Create two catalogs with the same group ID structure
136+
catalogs := []oscaltypes.Catalog{
137+
{
138+
UUID: "D20DB907-B87D-4D12-8760-D36FDB7A1B31",
139+
Metadata: oscaltypes.Metadata{
140+
Title: "Catalog 1",
141+
},
142+
Groups: &[]oscaltypes.Group{
143+
{
144+
ID: "G-1",
145+
Title: "Group 1",
146+
Controls: &[]oscaltypes.Control{
147+
{
148+
ID: "G-1.1",
149+
Title: "Control 1.1",
150+
},
151+
},
152+
},
153+
},
154+
},
155+
{
156+
UUID: "D20DB907-B87D-4D12-8760-D36FDB7A1B32",
157+
Metadata: oscaltypes.Metadata{
158+
Title: "Catalog 2",
159+
},
160+
Groups: &[]oscaltypes.Group{
161+
{
162+
ID: "G-1",
163+
Title: "Group 1",
164+
Controls: &[]oscaltypes.Control{
165+
{
166+
ID: "G-1.1",
167+
Title: "Control 2.1",
168+
},
169+
},
170+
},
171+
},
172+
},
173+
}
174+
for _, catalog := range catalogs {
175+
rec := httptest.NewRecorder()
176+
reqBody, _ := json.Marshal(catalog)
177+
req := httptest.NewRequest(http.MethodPost, "/api/oscal/catalogs", bytes.NewReader(reqBody))
178+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
179+
server.E().ServeHTTP(rec, req)
180+
assert.Equal(suite.T(), http.StatusCreated, rec.Code)
181+
response := &handler.GenericDataResponse[oscaltypes.Catalog]{}
182+
err = json.Unmarshal(rec.Body.Bytes(), response)
183+
suite.Require().NoError(err)
184+
}
185+
186+
// Now if we call to check the children for each catalogs' first group, we should only see 1 item
187+
188+
// The first catalog's group should have the Title Group 1
189+
rec := httptest.NewRecorder()
190+
req := httptest.NewRequest(http.MethodGet, "/api/oscal/catalogs/D20DB907-B87D-4D12-8760-D36FDB7A1B31/groups/G-1/controls", bytes.NewReader([]byte{}))
191+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
192+
server.E().ServeHTTP(rec, req)
193+
assert.Equal(suite.T(), http.StatusOK, rec.Code)
194+
response := &handler.GenericDataListResponse[oscaltypes.Control]{}
195+
err = json.Unmarshal(rec.Body.Bytes(), response)
196+
suite.Require().NoError(err)
197+
suite.Len(response.Data, 1)
198+
suite.Equal(response.Data[0].Title, "Control 1.1")
199+
200+
// The second catalog's group should have the Title Group 1
201+
rec = httptest.NewRecorder()
202+
req = httptest.NewRequest(http.MethodGet, "/api/oscal/catalogs/D20DB907-B87D-4D12-8760-D36FDB7A1B32/groups/G-1/controls", bytes.NewReader([]byte{}))
203+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
204+
server.E().ServeHTTP(rec, req)
205+
assert.Equal(suite.T(), http.StatusOK, rec.Code)
206+
response = &handler.GenericDataListResponse[oscaltypes.Control]{}
207+
err = json.Unmarshal(rec.Body.Bytes(), response)
208+
suite.Require().NoError(err)
209+
suite.Len(response.Data, 1)
210+
suite.Equal(response.Data[0].Title, "Control 2.1")
211+
}
212+
213+
// TestDuplicateCatalogChildControlID ensures that when multiple catalogs have control children with the same ID,
214+
// their children endpoints only returned the relevant controls.
215+
// This is to prevent a future regression where searching for child controls in a catalog, would return all the controls
216+
// with a matching ID, rather than only the ones which belong to a catalog.
217+
func (suite *CatalogApiIntegrationSuite) TestDuplicateCatalogChildControlID() {
218+
logger, _ := zap.NewDevelopment()
219+
220+
err := suite.Migrator.Refresh()
221+
suite.Require().NoError(err)
222+
223+
server := api.NewServer(context.Background(), logger.Sugar())
224+
RegisterHandlers(server, logger.Sugar(), suite.DB)
225+
226+
// Create two catalogs with the same group ID structure
227+
catalogs := []oscaltypes.Catalog{
228+
{
229+
UUID: "D20DB907-B87D-4D12-8760-D36FDB7A1B31",
230+
Metadata: oscaltypes.Metadata{
231+
Title: "Catalog 1",
232+
},
233+
Controls: &[]oscaltypes.Control{
234+
{
235+
ID: "G-1",
236+
Title: "Group 1",
237+
Controls: &[]oscaltypes.Control{
238+
{
239+
ID: "G-1.1",
240+
Title: "Control 1.1",
241+
},
242+
},
243+
},
244+
},
245+
},
246+
{
247+
UUID: "D20DB907-B87D-4D12-8760-D36FDB7A1B32",
248+
Metadata: oscaltypes.Metadata{
249+
Title: "Catalog 2",
250+
},
251+
Controls: &[]oscaltypes.Control{
252+
{
253+
ID: "G-1",
254+
Title: "Group 1",
255+
Controls: &[]oscaltypes.Control{
256+
{
257+
ID: "G-1.1",
258+
Title: "Control 2.1",
259+
},
260+
},
261+
},
262+
},
263+
},
264+
}
265+
for _, catalog := range catalogs {
266+
rec := httptest.NewRecorder()
267+
reqBody, _ := json.Marshal(catalog)
268+
req := httptest.NewRequest(http.MethodPost, "/api/oscal/catalogs", bytes.NewReader(reqBody))
269+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
270+
server.E().ServeHTTP(rec, req)
271+
assert.Equal(suite.T(), http.StatusCreated, rec.Code)
272+
response := &handler.GenericDataResponse[oscaltypes.Catalog]{}
273+
err = json.Unmarshal(rec.Body.Bytes(), response)
274+
suite.Require().NoError(err)
275+
}
276+
277+
// Now if we call to check the children for each catalogs' first group, we should only see 1 item
278+
279+
// The first catalog's group should have the Title Group 1
280+
rec := httptest.NewRecorder()
281+
req := httptest.NewRequest(http.MethodGet, "/api/oscal/catalogs/D20DB907-B87D-4D12-8760-D36FDB7A1B31/controls/G-1/controls", bytes.NewReader([]byte{}))
282+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
283+
server.E().ServeHTTP(rec, req)
284+
assert.Equal(suite.T(), http.StatusOK, rec.Code)
285+
response := &handler.GenericDataListResponse[oscaltypes.Control]{}
286+
err = json.Unmarshal(rec.Body.Bytes(), response)
287+
suite.Require().NoError(err)
288+
suite.Len(response.Data, 1)
289+
suite.Equal(response.Data[0].Title, "Control 1.1")
290+
291+
// The second catalog's group should have the Title Group 1
292+
rec = httptest.NewRecorder()
293+
req = httptest.NewRequest(http.MethodGet, "/api/oscal/catalogs/D20DB907-B87D-4D12-8760-D36FDB7A1B32/controls/G-1/controls", bytes.NewReader([]byte{}))
294+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
295+
server.E().ServeHTTP(rec, req)
296+
assert.Equal(suite.T(), http.StatusOK, rec.Code)
297+
response = &handler.GenericDataListResponse[oscaltypes.Control]{}
298+
err = json.Unmarshal(rec.Body.Bytes(), response)
299+
suite.Require().NoError(err)
300+
suite.Len(response.Data, 1)
301+
suite.Equal(response.Data[0].Title, "Control 2.1")
302+
}

0 commit comments

Comments
 (0)