Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b2892c3

Browse files
authored
test: Increase test coverage on auditable resources (coder#7038)
* test: Increase test coverage on auditable resources When adding a new audit resource, we also need to add it to the function switch statements. This is a likely mistake, now a unit test will check this for you
1 parent 24d8644 commit b2892c3

File tree

6 files changed

+114
-5
lines changed

6 files changed

+114
-5
lines changed

coderd/audit/request.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func ResourceTarget[T Auditable](tgt T) string {
7878
return ""
7979
case database.License:
8080
return strconv.Itoa(int(typed.ID))
81+
case database.WorkspaceProxy:
82+
return typed.Name
8183
default:
8284
panic(fmt.Sprintf("unknown resource %T", tgt))
8385
}
@@ -103,13 +105,15 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
103105
return typed.UserID
104106
case database.License:
105107
return typed.UUID
108+
case database.WorkspaceProxy:
109+
return typed.ID
106110
default:
107111
panic(fmt.Sprintf("unknown resource %T", tgt))
108112
}
109113
}
110114

111115
func ResourceType[T Auditable](tgt T) database.ResourceType {
112-
switch any(tgt).(type) {
116+
switch typed := any(tgt).(type) {
113117
case database.Template:
114118
return database.ResourceTypeTemplate
115119
case database.TemplateVersion:
@@ -128,8 +132,10 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
128132
return database.ResourceTypeApiKey
129133
case database.License:
130134
return database.ResourceTypeLicense
135+
case database.WorkspaceProxy:
136+
return database.ResourceTypeWorkspaceProxy
131137
default:
132-
panic(fmt.Sprintf("unknown resource %T", tgt))
138+
panic(fmt.Sprintf("unknown resource %T", typed))
133139
}
134140
}
135141

coderd/database/dump.sql

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- It's not possible to drop enum values from enum types, so the UP has "IF NOT
2+
-- EXISTS".
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'workspace_proxy';

coderd/database/models.go

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/audit/table_internal_test.go

+97-1
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package audit
22

33
import (
4+
"fmt"
45
"go/types"
6+
"strings"
57
"testing"
68

79
"github.com/stretchr/testify/assert"
810
"github.com/stretchr/testify/require"
911
"golang.org/x/tools/go/packages"
12+
13+
"github.com/coder/coder/coderd/audit"
14+
"github.com/coder/coder/coderd/database"
15+
"github.com/coder/coder/coderd/util/slice"
1016
)
1117

1218
// TestAuditableResources ensures that all auditable resources are included in
1319
// the Auditable interface and vice versa.
20+
//
21+
//nolint:tparallel
1422
func TestAuditableResources(t *testing.T) {
1523
t.Parallel()
1624

1725
pkgs, err := packages.Load(&packages.Config{
18-
Mode: packages.NeedTypes,
26+
Mode: packages.NeedTypes | packages.NeedDeps,
1927
}, "../../coderd/audit")
2028
require.NoError(t, err)
2129

@@ -37,13 +45,15 @@ func TestAuditableResources(t *testing.T) {
3745
require.True(t, ok, "expected Auditable to be a union")
3846

3947
found := make(map[string]bool)
48+
expectedList := make([]string, 0)
4049
// Now we check we have all the resources in the AuditableResources
4150
for i := 0; i < unionType.Len(); i++ {
4251
// All types come across like 'github.com/coder/coder/coderd/database.<type>'
4352
typeName := unionType.Term(i).Type().String()
4453
_, ok := AuditableResources[typeName]
4554
assert.True(t, ok, "missing resource %q from AuditableResources", typeName)
4655
found[typeName] = true
56+
expectedList = append(expectedList, typeName)
4757
}
4858

4959
// Also check that all resources in the table are in the union. We could
@@ -52,4 +62,90 @@ func TestAuditableResources(t *testing.T) {
5262
_, ok := found[name]
5363
assert.True(t, ok, "extra resource %q found in AuditableResources", name)
5464
}
65+
66+
// Various functions that have switch statements to include all Auditable
67+
// resources. Make sure we have all types supported.
68+
// nolint:paralleltest
69+
t.Run("ResourceID", func(t *testing.T) {
70+
// The function being tested, provided here to make it easier to find
71+
_ = audit.ResourceID[database.APIKey]
72+
testAuditFunctionWithSwitch(t, auditPkg, "ResourceID", expectedList)
73+
})
74+
75+
// nolint:paralleltest
76+
t.Run("ResourceType", func(t *testing.T) {
77+
// The function being tested, provided here to make it easier to find
78+
_ = audit.ResourceType[database.APIKey]
79+
testAuditFunctionWithSwitch(t, auditPkg, "ResourceType", expectedList)
80+
})
81+
82+
// nolint:paralleltest
83+
t.Run("ResourceTarget", func(t *testing.T) {
84+
// The function being tested, provided here to make it easier to find
85+
_ = audit.ResourceTarget[database.APIKey]
86+
testAuditFunctionWithSwitch(t, auditPkg, "ResourceTarget", expectedList)
87+
})
88+
}
89+
90+
// testAuditFunctionWithSwitch is a helper function to test that a function has
91+
// a typed switch statement that includes all the types in expectedTypes.
92+
func testAuditFunctionWithSwitch(t *testing.T, pkg *packages.Package, funcName string, expectedTypes []string) {
93+
t.Helper()
94+
95+
f, ok := pkg.Types.Scope().Lookup(funcName).(*types.Func)
96+
require.True(t, ok, fmt.Sprintf("expected %s to be a function", funcName))
97+
switchCases := findSwitchTypes(f)
98+
for _, expected := range expectedTypes {
99+
if !slice.Contains(switchCases, expected) {
100+
t.Errorf("%s switch statement is missing type %q. Include it in the switch case block", funcName, expected)
101+
}
102+
}
103+
for _, sc := range switchCases {
104+
if !slice.Contains(expectedTypes, sc) {
105+
t.Errorf("%s switch statement has unexpected type %q. Remove it from the switch case block", funcName, sc)
106+
}
107+
}
108+
}
109+
110+
// findSwitchTypes is a helper function to find all types a switch statement in
111+
// the function body of f has.
112+
func findSwitchTypes(f *types.Func) []string {
113+
caseTypes := make([]string, 0)
114+
switches := returnSwitchBlocks(f.Scope())
115+
for _, sc := range switches {
116+
scTypes := findCaseTypes(sc)
117+
caseTypes = append(caseTypes, scTypes...)
118+
}
119+
return caseTypes
120+
}
121+
122+
func returnSwitchBlocks(sc *types.Scope) []*types.Scope {
123+
switches := make([]*types.Scope, 0)
124+
for i := 0; i < sc.NumChildren(); i++ {
125+
child := sc.Child(i)
126+
cStr := child.String()
127+
// This is the easiest way to tell if it is a switch statement.
128+
if strings.Contains(cStr, "type switch scope") {
129+
switches = append(switches, child)
130+
}
131+
}
132+
return switches
133+
}
134+
135+
// findCaseTypes returns all case types in a typed switch statement. Excluding
136+
// the "Default:" case.
137+
func findCaseTypes(sc *types.Scope) []string {
138+
caseTypes := make([]string, 0)
139+
for i := 0; i < sc.NumChildren(); i++ {
140+
child := sc.Child(i)
141+
for _, name := range child.Names() {
142+
obj := child.Lookup(name).Type()
143+
typeName := obj.String()
144+
// Ignore the "Default:" case
145+
if typeName != "any" {
146+
caseTypes = append(caseTypes, typeName)
147+
}
148+
}
149+
}
150+
return caseTypes
55151
}

0 commit comments

Comments
 (0)