Skip to content

Commit 27cbf14

Browse files
committed
fix: make Policy.IncludeChildren and Policy.Global tri-state (*bool) so explicit false is serialized
1 parent bd03e36 commit 27cbf14

2 files changed

Lines changed: 245 additions & 2 deletions

File tree

policy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ type Policy struct {
1414
Operator PolicyOperator `json:"operator"`
1515
ViolationState PolicyViolationState `json:"violationState"`
1616
PolicyConditions []PolicyCondition `json:"policyConditions,omitempty"`
17-
IncludeChildren bool `json:"includeChildren,omitempty"`
18-
Global bool `json:"global,omitempty"`
17+
IncludeChildren *bool `json:"includeChildren,omitempty"`
18+
Global *bool `json:"global,omitempty"`
1919
Projects []Project `json:"projects,omitempty"`
2020
Tags []Tag `json:"tags,omitempty"`
2121
}

policy_test.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package dtrack
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func b(v bool) *bool { return &v }
11+
12+
func TestPolicyJSONEncoding_Bools(t *testing.T) {
13+
cases := []struct {
14+
name string
15+
in Policy
16+
want string
17+
}{
18+
{
19+
name: "omit both",
20+
in: Policy{Name: "x"},
21+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":""}`,
22+
},
23+
{
24+
name: "send false global",
25+
in: Policy{Name: "x", Global: b(false)},
26+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","global":false}`,
27+
},
28+
{
29+
name: "send true global",
30+
in: Policy{Name: "x", Global: b(true)},
31+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","global":true}`,
32+
},
33+
{
34+
name: "send false includeChildren",
35+
in: Policy{Name: "x", IncludeChildren: b(false)},
36+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","includeChildren":false}`,
37+
},
38+
{
39+
name: "send true includeChildren",
40+
in: Policy{Name: "x", IncludeChildren: b(true)},
41+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","includeChildren":true}`,
42+
},
43+
{
44+
name: "both flags",
45+
in: Policy{Name: "x", Global: b(false), IncludeChildren: b(true)},
46+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","includeChildren":true,"global":false}`,
47+
},
48+
}
49+
50+
for _, tc := range cases {
51+
t.Run(tc.name, func(t *testing.T) {
52+
// Marshal the Policy struct
53+
got, err := json.Marshal(tc.in)
54+
require.NoError(t, err)
55+
56+
// Compare by unmarshaling both to ensure key order insensitivity
57+
var gotMap, wantMap map[string]interface{}
58+
err = json.Unmarshal(got, &gotMap)
59+
require.NoError(t, err)
60+
err = json.Unmarshal([]byte(tc.want), &wantMap)
61+
require.NoError(t, err)
62+
63+
require.Equal(t, wantMap, gotMap)
64+
})
65+
}
66+
}
67+
68+
func TestPolicyJSONDecoding_Bools(t *testing.T) {
69+
cases := []struct {
70+
name string
71+
json string
72+
wantGlobal *bool
73+
wantIncludeCh *bool
74+
}{
75+
{
76+
name: "omit both",
77+
json: `{"name":"x","operator":"","violationState":""}`,
78+
wantGlobal: nil,
79+
wantIncludeCh: nil,
80+
},
81+
{
82+
name: "receive false global",
83+
json: `{"name":"x","operator":"","violationState":"","global":false}`,
84+
wantGlobal: b(false),
85+
wantIncludeCh: nil,
86+
},
87+
{
88+
name: "receive true global",
89+
json: `{"name":"x","operator":"","violationState":"","global":true}`,
90+
wantGlobal: b(true),
91+
wantIncludeCh: nil,
92+
},
93+
{
94+
name: "receive false includeChildren",
95+
json: `{"name":"x","operator":"","violationState":"","includeChildren":false}`,
96+
wantGlobal: nil,
97+
wantIncludeCh: b(false),
98+
},
99+
{
100+
name: "receive true includeChildren",
101+
json: `{"name":"x","operator":"","violationState":"","includeChildren":true}`,
102+
wantGlobal: nil,
103+
wantIncludeCh: b(true),
104+
},
105+
{
106+
name: "both flags",
107+
json: `{"name":"x","operator":"","violationState":"","includeChildren":true,"global":false}`,
108+
wantGlobal: b(false),
109+
wantIncludeCh: b(true),
110+
},
111+
}
112+
113+
for _, tc := range cases {
114+
t.Run(tc.name, func(t *testing.T) {
115+
var p Policy
116+
err := json.Unmarshal([]byte(tc.json), &p)
117+
require.NoError(t, err)
118+
119+
// Check Global field
120+
if tc.wantGlobal == nil {
121+
require.Nil(t, p.Global)
122+
} else {
123+
require.NotNil(t, p.Global)
124+
require.Equal(t, *tc.wantGlobal, *p.Global)
125+
}
126+
127+
// Check IncludeChildren field
128+
if tc.wantIncludeCh == nil {
129+
require.Nil(t, p.IncludeChildren)
130+
} else {
131+
require.NotNil(t, p.IncludeChildren)
132+
require.Equal(t, *tc.wantIncludeCh, *p.IncludeChildren)
133+
}
134+
})
135+
}
136+
}
137+
138+
func TestPolicyJSONEncoding_BoolsWithOptionalBoolOf(t *testing.T) {
139+
cases := []struct {
140+
name string
141+
in Policy
142+
want string
143+
}{
144+
{
145+
name: "OptionalBoolOf true",
146+
in: Policy{Name: "x", Global: OptionalBoolOf(true)},
147+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","global":true}`,
148+
},
149+
{
150+
name: "OptionalBoolOf false",
151+
in: Policy{Name: "x", Global: OptionalBoolOf(false)},
152+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","global":false}`,
153+
},
154+
{
155+
name: "both with OptionalBoolOf",
156+
in: Policy{Name: "x", Global: OptionalBoolOf(true), IncludeChildren: OptionalBoolOf(false)},
157+
want: `{"uuid":"00000000-0000-0000-0000-000000000000","name":"x","operator":"","violationState":"","includeChildren":false,"global":true}`,
158+
},
159+
}
160+
161+
for _, tc := range cases {
162+
t.Run(tc.name, func(t *testing.T) {
163+
// Marshal the Policy struct
164+
got, err := json.Marshal(tc.in)
165+
require.NoError(t, err)
166+
167+
// Compare by unmarshaling both to ensure key order insensitivity
168+
var gotMap, wantMap map[string]interface{}
169+
err = json.Unmarshal(got, &gotMap)
170+
require.NoError(t, err)
171+
err = json.Unmarshal([]byte(tc.want), &wantMap)
172+
require.NoError(t, err)
173+
174+
require.Equal(t, wantMap, gotMap)
175+
})
176+
}
177+
}
178+
179+
func TestPolicyJSONRoundTrip_Bools(t *testing.T) {
180+
cases := []struct {
181+
name string
182+
in Policy
183+
}{
184+
{
185+
name: "nil both",
186+
in: Policy{Name: "x", Operator: PolicyOperatorAll, ViolationState: PolicyViolationStateInfo},
187+
},
188+
{
189+
name: "false global",
190+
in: Policy{Name: "x", Operator: PolicyOperatorAll, ViolationState: PolicyViolationStateInfo, Global: b(false)},
191+
},
192+
{
193+
name: "true global",
194+
in: Policy{Name: "x", Operator: PolicyOperatorAll, ViolationState: PolicyViolationStateInfo, Global: b(true)},
195+
},
196+
{
197+
name: "false includeChildren",
198+
in: Policy{Name: "x", Operator: PolicyOperatorAny, ViolationState: PolicyViolationStateWarn, IncludeChildren: b(false)},
199+
},
200+
{
201+
name: "true includeChildren",
202+
in: Policy{Name: "x", Operator: PolicyOperatorAny, ViolationState: PolicyViolationStateWarn, IncludeChildren: b(true)},
203+
},
204+
{
205+
name: "both flags set",
206+
in: Policy{Name: "x", Operator: PolicyOperatorAll, ViolationState: PolicyViolationStateFail, Global: b(true), IncludeChildren: b(false)},
207+
},
208+
}
209+
210+
for _, tc := range cases {
211+
t.Run(tc.name, func(t *testing.T) {
212+
// Marshal to JSON
213+
jsonBytes, err := json.Marshal(tc.in)
214+
require.NoError(t, err)
215+
216+
// Unmarshal back to struct
217+
var out Policy
218+
err = json.Unmarshal(jsonBytes, &out)
219+
require.NoError(t, err)
220+
221+
// Compare relevant fields
222+
require.Equal(t, tc.in.Name, out.Name)
223+
require.Equal(t, tc.in.Operator, out.Operator)
224+
require.Equal(t, tc.in.ViolationState, out.ViolationState)
225+
226+
// Check Global field
227+
if tc.in.Global == nil {
228+
require.Nil(t, out.Global)
229+
} else {
230+
require.NotNil(t, out.Global)
231+
require.Equal(t, *tc.in.Global, *out.Global)
232+
}
233+
234+
// Check IncludeChildren field
235+
if tc.in.IncludeChildren == nil {
236+
require.Nil(t, out.IncludeChildren)
237+
} else {
238+
require.NotNil(t, out.IncludeChildren)
239+
require.Equal(t, *tc.in.IncludeChildren, *out.IncludeChildren)
240+
}
241+
})
242+
}
243+
}

0 commit comments

Comments
 (0)