Skip to content

Commit feebf2b

Browse files
committed
add unit test to verify subset of fields
1 parent 63ae42d commit feebf2b

File tree

6 files changed

+160
-62
lines changed

6 files changed

+160
-62
lines changed

coderd/database/dbgen/dbgen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func WorkspaceAgentScriptTiming(t testing.TB, db database.Store, orig database.W
232232
return timing
233233
}
234234

235-
func Workspace(t testing.TB, db database.Store, orig database.Workspace) database.Workspace {
235+
func Workspace(t testing.TB, db database.Store, orig database.WorkspaceTable) database.WorkspaceTable {
236236
t.Helper()
237237

238238
workspace, err := db.InsertWorkspace(genCtx, database.InsertWorkspaceParams{

coderd/database/gentest/models_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ func TestViewSubsetWorkspaceBuild(t *testing.T) {
6565
}
6666
}
6767

68+
// TestViewSubsetWorkspace ensures WorkspaceTable is a subset of Workspace
69+
func TestViewSubsetWorkspace(t *testing.T) {
70+
t.Parallel()
71+
table := reflect.TypeOf(database.WorkspaceTable{})
72+
joined := reflect.TypeOf(database.Workspace{})
73+
74+
tableFields := allFields(table)
75+
joinedFields := allFields(joined)
76+
if !assert.Subset(t, fieldNames(joinedFields), fieldNames(tableFields), "table is not subset") {
77+
t.Log("Some fields were added to the Workspace Table without updating the 'workspaces_expanded' view.")
78+
t.Log("See migration 000262_workspace_with_names.up.sql to create the view.")
79+
}
80+
}
81+
6882
func fieldNames(fields []reflect.StructField) []string {
6983
names := make([]string, len(fields))
7084
for i, field := range fields {

coderd/database/modelqueries_internal_test.go

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package database
22

33
import (
44
"fmt"
5-
"reflect"
65
"testing"
76

87
"github.com/stretchr/testify/require"
8+
9+
"github.com/coder/coder/v2/testutil"
910
)
1011

1112
func TestIsAuthorizedQuery(t *testing.T) {
@@ -22,68 +23,12 @@ func TestWorkspaceTableConvert(t *testing.T) {
2223
t.Parallel()
2324

2425
var workspace Workspace
25-
err := populateStruct(&workspace)
26+
err := testutil.PopulateStruct(&workspace, nil)
2627
require.NoError(t, err)
2728

28-
}
29-
30-
func populateStruct(s interface{}) error {
31-
v := reflect.ValueOf(s)
32-
if v.Kind() != reflect.Ptr || v.IsNil() {
33-
return fmt.Errorf("s must be a non-nil pointer")
34-
}
35-
36-
v = v.Elem()
37-
if v.Kind() != reflect.Struct {
38-
return fmt.Errorf("s must be a pointer to a struct")
39-
}
40-
41-
t := v.Type()
42-
for i := 0; i < t.NumField(); i++ {
43-
field := t.Field(i)
44-
fieldName := field.Name
45-
46-
fieldValue := v.Field(i)
47-
if !fieldValue.CanSet() {
48-
continue // Skip if field is unexported
49-
}
29+
workspace.WorkspaceTable()
30+
require.JSONEq(t)
5031

51-
switch fieldValue.Kind() {
52-
case reflect.Struct:
53-
if err := populateStruct(fieldValue.Addr().Interface()); err != nil {
54-
return fmt.Errorf("%s : %w", fieldName, err)
55-
}
56-
case reflect.String:
57-
fieldValue.SetString("foo")
58-
case reflect.Invalid:
59-
case reflect.Bool:
60-
case reflect.Int:
61-
case reflect.Int8:
62-
case reflect.Int16:
63-
case reflect.Int32:
64-
case reflect.Int64:
65-
case reflect.Uint:
66-
case reflect.Uint8:
67-
case reflect.Uint16:
68-
case reflect.Uint32:
69-
case reflect.Uint64:
70-
case reflect.Uintptr:
71-
case reflect.Float32:
72-
case reflect.Float64:
73-
case reflect.Complex64:
74-
case reflect.Complex128:
75-
case reflect.Array:
76-
case reflect.Chan:
77-
case reflect.Func:
78-
case reflect.Interface:
79-
case reflect.Map:
80-
case reflect.Pointer:
81-
case reflect.Slice:
82-
case reflect.UnsafePointer:
83-
default:
84-
return fmt.Errorf("unsupported kind %s", fieldValue.Kind())
85-
}
86-
}
32+
fmt.Println(workspace)
8733

88-
return nil
8934
}

testutil/reflect.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package testutil
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"time"
7+
)
8+
9+
type Random struct {
10+
String func() string
11+
Bool func() bool
12+
Int func() int64
13+
Uint func() uint64
14+
Float func() float64
15+
Complex func() complex128
16+
}
17+
18+
func NewRandom() *Random {
19+
// Guaranteed to be random...
20+
return &Random{
21+
String: func() string { return "foo" },
22+
Bool: func() bool { return true },
23+
Int: func() int64 { return 500 },
24+
Uint: func() uint64 { return 126 },
25+
Float: func() float64 { return 3.14 },
26+
Complex: func() complex128 { return 6.24 },
27+
}
28+
}
29+
30+
// PopulateStruct does a best effort to populate a struct with random values.
31+
func PopulateStruct(s interface{}, r *Random) error {
32+
if r == nil {
33+
r = NewRandom()
34+
}
35+
36+
v := reflect.ValueOf(s)
37+
if v.Kind() != reflect.Ptr || v.IsNil() {
38+
return fmt.Errorf("s must be a non-nil pointer")
39+
}
40+
41+
v = v.Elem()
42+
if v.Kind() != reflect.Struct {
43+
return fmt.Errorf("s must be a pointer to a struct")
44+
}
45+
46+
t := v.Type()
47+
for i := 0; i < t.NumField(); i++ {
48+
field := t.Field(i)
49+
fieldName := field.Name
50+
51+
fieldValue := v.Field(i)
52+
if !fieldValue.CanSet() {
53+
continue // Skip if field is unexported
54+
}
55+
56+
nv, err := populateValue(fieldValue, r)
57+
if err != nil {
58+
return fmt.Errorf("%s : %w", fieldName, err)
59+
}
60+
v.Field(i).Set(nv)
61+
}
62+
63+
return nil
64+
}
65+
66+
func populateValue(v reflect.Value, r *Random) (reflect.Value, error) {
67+
var err error
68+
69+
// Handle some special cases
70+
switch v.Type() {
71+
case reflect.TypeOf(time.Time{}):
72+
v.Set(reflect.ValueOf(time.Date(2020, 5, 2, 5, 19, 21, 30, time.UTC)))
73+
return v, nil
74+
}
75+
76+
switch v.Kind() {
77+
case reflect.Struct:
78+
if err := PopulateStruct(v.Addr().Interface(), r); err != nil {
79+
return v, err
80+
}
81+
case reflect.String:
82+
v.SetString(r.String())
83+
case reflect.Bool:
84+
v.SetBool(true)
85+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
86+
v.SetInt(r.Int())
87+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
88+
v.SetUint(r.Uint())
89+
case reflect.Float32, reflect.Float64:
90+
v.SetFloat(r.Float())
91+
case reflect.Complex64, reflect.Complex128:
92+
v.SetComplex(r.Complex())
93+
case reflect.Array:
94+
for i := 0; i < v.Len(); i++ {
95+
nv, err := populateValue(v.Index(i), r)
96+
if err != nil {
97+
return v, fmt.Errorf("array index %d : %w", i, err)
98+
}
99+
v.Index(i).Set(nv)
100+
}
101+
case reflect.Map:
102+
m := reflect.MakeMap(v.Type())
103+
104+
// Set a value in the map
105+
k := reflect.New(v.Type().Key())
106+
kv := reflect.New(v.Type().Elem())
107+
k, err = populateValue(k, r)
108+
if err != nil {
109+
return v, fmt.Errorf("map key : %w", err)
110+
}
111+
kv, err = populateValue(kv, r)
112+
if err != nil {
113+
return v, fmt.Errorf("map value : %w", err)
114+
}
115+
116+
m.SetMapIndex(k, kv)
117+
return m, nil
118+
case reflect.Pointer:
119+
return populateValue(v.Elem(), r)
120+
case reflect.Slice:
121+
s := reflect.MakeSlice(v.Type(), 2, 2)
122+
sv, err := populateValue(reflect.New(v.Type().Elem()), r)
123+
if err != nil {
124+
return v, fmt.Errorf("slice value : %w", err)
125+
}
126+
127+
s.Index(0).Set(sv)
128+
s.Index(1).Set(sv)
129+
//reflect.AppendSlice(s, sv)
130+
131+
return s, nil
132+
case reflect.Uintptr, reflect.UnsafePointer, reflect.Chan, reflect.Func, reflect.Interface:
133+
// Unsupported
134+
return v, fmt.Errorf("%s is not supported", v.Kind())
135+
default:
136+
return v, fmt.Errorf("unsupported kind %s", v.Kind())
137+
}
138+
return v, nil
139+
}

0 commit comments

Comments
 (0)