Skip to content

Commit f127339

Browse files
committed
test: refactoring for unittests
1 parent f5a4f3a commit f127339

File tree

6 files changed

+347
-4
lines changed

6 files changed

+347
-4
lines changed

.testcoverage.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ exclude:
33
- ^controller/testSupport # exclude test support files
44
- mocks # exclude generated mock files
55
- ^test/openfga
6+
- logger/testlogger
67

config/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,17 @@ func BindConfigToFlags(v *viper.Viper, cmd *cobra.Command, config any) error {
212212
return nil
213213
}
214214

215+
// GenerateFlagSet is exported for testing.
216+
func GenerateFlagSet(config any) (*pflag.FlagSet, error) {
217+
return generateFlagSet(config)
218+
}
219+
220+
// UnmarshalIntoStruct is exported for testing.
221+
func UnmarshalIntoStruct(v *viper.Viper, cfg any) func() {
222+
return unmarshalIntoStruct(v, cfg)
223+
}
224+
225+
// unmarshalIntoStruct returns a function that unmarshals viper config into cfg and panics on error.
215226
func unmarshalIntoStruct(v *viper.Viper, cfg any) func() {
216227
return func() {
217228
if err := v.Unmarshal(cfg); err != nil {

config/config_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,34 @@ func TestNewDefaultConfig(t *testing.T) {
168168
err = v.Unmarshal(&config.CommonServiceConfig{})
169169
assert.NoError(t, err)
170170
}
171+
172+
func TestGenerateFlagSetUnsupportedType(t *testing.T) {
173+
type test struct {
174+
UnsupportedField []string `mapstructure:"unsupported-field"`
175+
}
176+
testStruct := test{}
177+
err := config.BindConfigToFlags(viper.New(), &cobra.Command{}, &testStruct)
178+
assert.Error(t, err)
179+
}
180+
181+
func TestGenerateFlagSetInvalidStruct(t *testing.T) {
182+
notStruct := 123
183+
_, err := config.GenerateFlagSet(notStruct)
184+
assert.Error(t, err)
185+
}
186+
187+
func TestUnmarshalIntoStructPanic(t *testing.T) {
188+
v := viper.New()
189+
cfg := struct {
190+
InvalidField int `mapstructure:"invalid-field"`
191+
}{}
192+
// Set a string value to cause unmarshal error for int field
193+
v.Set("invalid-field", "not-an-int")
194+
defer func() {
195+
if r := recover(); r == nil {
196+
t.Errorf("expected panic but did not panic")
197+
}
198+
}()
199+
unmarshal := config.UnmarshalIntoStruct(v, &cfg)
200+
unmarshal()
201+
}

controller/lifecycle/lifecycle_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,3 +1327,107 @@ func TestUpdateStatus(t *testing.T) {
13271327
assert.Equal(t, "status field not found in current object", err.Error())
13281328
})
13291329
}
1330+
1331+
func TestAddFinalizersIfNeeded(t *testing.T) {
1332+
instance := &pmtesting.TestApiObject{ObjectMeta: metav1.ObjectMeta{Name: "instance1"}}
1333+
fakeClient := pmtesting.CreateFakeClient(t, instance)
1334+
sub := pmtesting.FinalizerSubroutine{Client: fakeClient}
1335+
// Should add finalizer
1336+
err := AddFinalizersIfNeeded(context.Background(), fakeClient, instance, []subroutine.Subroutine{sub}, false)
1337+
assert.NoError(t, err)
1338+
assert.Contains(t, instance.Finalizers, pmtesting.SubroutineFinalizer)
1339+
1340+
// Should not add if readonly
1341+
instance2 := &pmtesting.TestApiObject{}
1342+
err = AddFinalizersIfNeeded(context.Background(), fakeClient, instance2, []subroutine.Subroutine{sub}, true)
1343+
assert.NoError(t, err)
1344+
assert.NotContains(t, instance2.Finalizers, pmtesting.SubroutineFinalizer)
1345+
1346+
// Should not add if deletion timestamp is set
1347+
now := metav1.Now()
1348+
instance3 := &pmtesting.TestApiObject{ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &now}}
1349+
err = AddFinalizersIfNeeded(context.Background(), fakeClient, instance3, []subroutine.Subroutine{sub}, false)
1350+
assert.NoError(t, err)
1351+
}
1352+
1353+
func TestAddFinalizerIfNeeded(t *testing.T) {
1354+
instance := &pmtesting.TestApiObject{}
1355+
sub := pmtesting.FinalizerSubroutine{}
1356+
// Should add and return true
1357+
added := AddFinalizerIfNeeded(instance, sub)
1358+
assert.True(t, added)
1359+
// Should not add again
1360+
added = AddFinalizerIfNeeded(instance, sub)
1361+
assert.False(t, added)
1362+
}
1363+
1364+
func TestRemoveFinalizerIfNeeded(t *testing.T) {
1365+
instance := &pmtesting.TestApiObject{ObjectMeta: metav1.ObjectMeta{Name: "instance1"}}
1366+
sub := pmtesting.FinalizerSubroutine{}
1367+
AddFinalizerIfNeeded(instance, sub)
1368+
fakeClient := pmtesting.CreateFakeClient(t, instance)
1369+
// Should remove finalizer if not readonly and RequeueAfter == 0
1370+
res := ctrl.Result{}
1371+
err := removeFinalizerIfNeeded(context.Background(), instance, sub, res, false, fakeClient)
1372+
assert.Nil(t, err)
1373+
assert.NotContains(t, instance.Finalizers, pmtesting.SubroutineFinalizer)
1374+
1375+
// Should not remove if readonly
1376+
AddFinalizerIfNeeded(instance, sub)
1377+
err = removeFinalizerIfNeeded(context.Background(), instance, sub, res, true, fakeClient)
1378+
assert.Nil(t, err)
1379+
assert.Contains(t, instance.Finalizers, pmtesting.SubroutineFinalizer)
1380+
1381+
// Should not remove if RequeueAfter > 0
1382+
res = ctrl.Result{RequeueAfter: 1}
1383+
err = removeFinalizerIfNeeded(context.Background(), instance, sub, res, false, fakeClient)
1384+
assert.Nil(t, err)
1385+
}
1386+
1387+
func TestContainsFinalizer(t *testing.T) {
1388+
instance := &pmtesting.TestApiObject{}
1389+
sub := pmtesting.FinalizerSubroutine{}
1390+
assert.False(t, containsFinalizer(instance, sub.Finalizers()))
1391+
AddFinalizerIfNeeded(instance, sub)
1392+
assert.True(t, containsFinalizer(instance, sub.Finalizers()))
1393+
}
1394+
1395+
func TestMarkResourceAsFinal(t *testing.T) {
1396+
instance := &pmtesting.ImplementingSpreadReconciles{}
1397+
logcfg := logger.DefaultConfig()
1398+
logcfg.NoJSON = true
1399+
log, _ := logger.New(logcfg)
1400+
conds := []metav1.Condition{}
1401+
mgr := &pmtesting.TestLifecycleManager{Logger: log}
1402+
MarkResourceAsFinal(instance, log, conds, metav1.ConditionTrue, mgr)
1403+
assert.Equal(t, instance.Status.ObservedGeneration, instance.Generation)
1404+
}
1405+
1406+
func TestHandleClientError(t *testing.T) {
1407+
log := testlogger.New().Logger
1408+
result, err := HandleClientError("msg", log, fmt.Errorf("err"), true, sentry.Tags{})
1409+
assert.Error(t, err)
1410+
assert.Equal(t, ctrl.Result{}, result)
1411+
}
1412+
1413+
func TestHandleOperatorError(t *testing.T) {
1414+
log := testlogger.New().Logger
1415+
opErr := operrors.NewOperatorError(fmt.Errorf("err"), false, false)
1416+
result, err := HandleOperatorError(context.Background(), opErr, "msg", true, log)
1417+
assert.Nil(t, err)
1418+
assert.Equal(t, ctrl.Result{}, result)
1419+
1420+
ctx := sentry.ContextWithSentryTags(context.Background(), sentry.Tags{"test": "tag"})
1421+
opErr = operrors.NewOperatorError(fmt.Errorf("err"), true, true)
1422+
result, err = HandleOperatorError(ctx, opErr, "msg", true, log)
1423+
assert.Error(t, err)
1424+
assert.Equal(t, ctrl.Result{}, result)
1425+
}
1426+
1427+
func TestValidateInterfaces(t *testing.T) {
1428+
log := testlogger.New().Logger
1429+
instance := &pmtesting.ImplementingSpreadReconciles{}
1430+
mgr := &pmtesting.TestLifecycleManager{Logger: log}
1431+
err := ValidateInterfaces(instance, log, mgr)
1432+
assert.NoError(t, err)
1433+
}

traces/otel.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,27 @@ type Config struct {
3030
CollectorEndpoint string `mapstructure:"tracing-config-collector-endpoint" description:"Set the tracing collector endpoint used to send traces to the collector"`
3131
}
3232

33+
// --- Wrappers for patching (default to real functions) ---
34+
var (
35+
resourceNewFunc = resource.New
36+
grpcNewClientFunc = grpc.NewClient
37+
otlptracegrpcNewFunc = otlptracegrpc.New // type: func(ctx context.Context, opts ...otlptracegrpc.Option) (*otlptrace.Exporter, error)
38+
stdouttraceNewFunc = stdouttrace.New // type: func(opts ...stdouttrace.Option) (*stdouttrace.Exporter, error)
39+
)
40+
3341
// InitProvider creates an OpenTelemetry provider for the concrete service.
3442
// If the collector in the destination endpoint isn't reachable, then the init function will return an error.
3543
func InitProvider(ctx context.Context, config Config) (func(ctx context.Context) error, error) {
3644
connCtx, cancel := context.WithTimeout(ctx, time.Second)
3745
defer cancel()
3846

39-
client, err := grpc.NewClient(config.CollectorEndpoint,
47+
client, err := grpcNewClientFunc(config.CollectorEndpoint,
4048
grpc.WithTransportCredentials(insecure.NewCredentials()))
4149
if err != nil {
4250
return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err)
4351
}
4452

45-
traceExporter, err := otlptracegrpc.New(connCtx, otlptracegrpc.WithGRPCConn(client))
53+
traceExporter, err := otlptracegrpcNewFunc(connCtx, otlptracegrpc.WithGRPCConn(client))
4654
if err != nil {
4755
return nil, fmt.Errorf("failed to create trace exporter: %w", err)
4856
}
@@ -58,7 +66,7 @@ func InitLocalProvider(ctx context.Context, config Config, exportToConsole bool)
5866
fileTarget = os.Stdout
5967
}
6068

61-
traceExporter, err := stdouttrace.New(
69+
traceExporter, err := stdouttraceNewFunc(
6270
stdouttrace.WithWriter(fileTarget),
6371
stdouttrace.WithPrettyPrint(),
6472
)
@@ -70,7 +78,7 @@ func InitLocalProvider(ctx context.Context, config Config, exportToConsole bool)
7078
}
7179

7280
func (c Config) initProvider(ctx context.Context, exporter sdkTrace.SpanExporter) (func(ctx context.Context) error, error) {
73-
res, err := resource.New(ctx,
81+
res, err := resourceNewFunc(ctx,
7482
resource.WithAttributes(
7583
semconv.ServiceName(c.ServiceName),
7684
semconv.ServiceVersion(c.ServiceVersion),

0 commit comments

Comments
 (0)