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

Lines changed: 8 additions & 2 deletions
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

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
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".
Lines changed: 1 addition & 0 deletions
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

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/audit/table_internal_test.go

Lines changed: 97 additions & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy