diff --git a/types/nullable_test.go b/types/nullable_test.go index 7fafd08..207250f 100644 --- a/types/nullable_test.go +++ b/types/nullable_test.go @@ -3,10 +3,11 @@ package types import ( "encoding/json" "fmt" + "testing" + "github.com/oapi-codegen/nullable" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) type SimpleStringNullableRequired struct { @@ -279,55 +280,54 @@ func TestNullableOptional_UnmarshalJSON(t *testing.T) { type testCase struct { name string json []byte - assert func(obj SimpleIntNullableOptional, t *testing.T) + assert func(t *testing.T, obj SimpleIntNullableOptional) } tests := []testCase{ { name: "when not provided", json: []byte(`{}`), - assert: func(obj SimpleIntNullableOptional, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableOptional) { t.Helper() - assert.Equalf(t, false, obj.ReplicaCount.IsSpecified(), "replica count should not be set") - assert.Equalf(t, false, obj.ReplicaCount.IsNull(), "replica count should not be null") + assert.Falsef(t, obj.ReplicaCount.IsSpecified(), "replica count should not be set") + assert.Falsef(t, obj.ReplicaCount.IsNull(), "replica count should not be null") }, }, { name: "when explicitly set to zero value", json: []byte(`{"replica_count":0}`), - assert: func(obj SimpleIntNullableOptional, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableOptional) { t.Helper() - assert.Equalf(t, true, obj.ReplicaCount.IsSpecified(), "replica count should be set") - assert.Equalf(t, false, obj.ReplicaCount.IsNull(), "replica count should not be null") + assert.Truef(t, obj.ReplicaCount.IsSpecified(), "replica count should be set") + assert.Falsef(t, obj.ReplicaCount.IsNull(), "replica count should not be null") val, err := obj.ReplicaCount.Get() require.NoError(t, err) assert.Equalf(t, 0, val, "replica count value should be 0") - }, }, { name: "when explicitly set to null value", json: []byte(`{"replica_count":null}`), - assert: func(obj SimpleIntNullableOptional, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableOptional) { t.Helper() - assert.Equalf(t, true, obj.ReplicaCount.IsSpecified(), "replica count should be set") - assert.Equalf(t, true, obj.ReplicaCount.IsNull(), "replica count should be null") + assert.Truef(t, obj.ReplicaCount.IsSpecified(), "replica count should be set") + assert.Truef(t, obj.ReplicaCount.IsNull(), "replica count should be null") }, }, { name: "when explicitly set to a specific value", json: []byte(`{"replica_count":5}`), - assert: func(obj SimpleIntNullableOptional, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableOptional) { t.Helper() - assert.Equalf(t, true, obj.ReplicaCount.IsSpecified(), "replica count should not be null") - assert.Equalf(t, false, obj.ReplicaCount.IsNull(), "replica count should not be null") + assert.Truef(t, obj.ReplicaCount.IsSpecified(), "replica count should be set") + assert.Falsef(t, obj.ReplicaCount.IsNull(), "replica count should not be null") val, err := obj.ReplicaCount.Get() require.NoError(t, err) @@ -340,7 +340,8 @@ func TestNullableOptional_UnmarshalJSON(t *testing.T) { var obj SimpleIntNullableOptional err := json.Unmarshal(tt.json, &obj) require.NoError(t, err) - tt.assert(obj, t) + + tt.assert(t, obj) }) } } @@ -403,7 +404,7 @@ func TestNullableRequired_UnmarshalJSON(t *testing.T) { type testCase struct { name string json []byte - assert func(obj SimpleIntNullableRequired, t *testing.T) + assert func(t *testing.T, obj SimpleIntNullableRequired) } tests := []testCase{ { @@ -412,22 +413,22 @@ func TestNullableRequired_UnmarshalJSON(t *testing.T) { // the behaviour name: "when not provided", json: []byte(`{}`), - assert: func(obj SimpleIntNullableRequired, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableRequired) { t.Helper() - assert.Equalf(t, false, obj.ReplicaCount.IsNull(), "replica count should not be null") - assert.Equalf(t, false, obj.ReplicaCount.IsSpecified(), "replica count should not be set") + assert.Falsef(t, obj.ReplicaCount.IsNull(), "replica count should not be null") + assert.Falsef(t, obj.ReplicaCount.IsSpecified(), "replica count should not be set") }, }, { name: "when explicitly set to zero value", json: []byte(`{"replica_count":0}`), - assert: func(obj SimpleIntNullableRequired, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableRequired) { t.Helper() - assert.Equalf(t, false, obj.ReplicaCount.IsNull(), "replica count should not be null") - assert.Equalf(t, true, obj.ReplicaCount.IsSpecified(), "replica count should be set") + assert.Falsef(t, obj.ReplicaCount.IsNull(), "replica count should not be null") + assert.Truef(t, obj.ReplicaCount.IsSpecified(), "replica count should be set") val, err := obj.ReplicaCount.Get() require.NoError(t, err) @@ -438,22 +439,22 @@ func TestNullableRequired_UnmarshalJSON(t *testing.T) { { name: "when explicitly set to null value", json: []byte(`{"replica_count":null}`), - assert: func(obj SimpleIntNullableRequired, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableRequired) { t.Helper() - assert.Equalf(t, true, obj.ReplicaCount.IsSpecified(), "replica count should be set") - assert.Equalf(t, true, obj.ReplicaCount.IsNull(), "replica count should be null") + assert.Truef(t, obj.ReplicaCount.IsSpecified(), "replica count should be set") + assert.Truef(t, obj.ReplicaCount.IsNull(), "replica count should be null") }, }, { name: "when explicitly set to a specific value", json: []byte(`{"replica_count":5}`), - assert: func(obj SimpleIntNullableRequired, t *testing.T) { + assert: func(t *testing.T, obj SimpleIntNullableRequired) { t.Helper() - assert.Equalf(t, false, obj.ReplicaCount.IsNull(), "replica count should not be null") - assert.Equalf(t, true, obj.ReplicaCount.IsSpecified(), "replica count should be set") + assert.Falsef(t, obj.ReplicaCount.IsNull(), "replica count should not be null") + assert.Truef(t, obj.ReplicaCount.IsSpecified(), "replica count should be set") val, err := obj.ReplicaCount.Get() require.NoError(t, err) @@ -466,35 +467,179 @@ func TestNullableRequired_UnmarshalJSON(t *testing.T) { var obj SimpleIntNullableRequired err := json.Unmarshal(tt.json, &obj) require.NoError(t, err) - tt.assert(obj, t) + + tt.assert(t, obj) + }) + } +} + +type ComplexNullable struct { + Config nullable.Nullable[Config] `json:"config,omitempty"` + Location nullable.Nullable[string] `json:"location"` + NodeCount nullable.Nullable[int] `json:"node_count,omitempty"` +} + +type Config struct { + CPU nullable.Nullable[string] `json:"cpu,omitempty"` + RAM nullable.Nullable[string] `json:"ram,omitempty"` +} + +func TestComplexNullable(t *testing.T) { + type testCase struct { + name string + jsonInput []byte + assert func(t *testing.T, obj ComplexNullable) + } + tests := []testCase{ + { + name: "complex object: empty value", + jsonInput: []byte(`{}`), + assert: func(t *testing.T, obj ComplexNullable) { + t.Helper() + + assert.Falsef(t, obj.Config.IsSpecified(), "config should not be set") + assert.Falsef(t, obj.Config.IsNull(), "config should not be null") + + assert.Falsef(t, obj.NodeCount.IsSpecified(), "node count should not be set") + assert.Falsef(t, obj.NodeCount.IsNull(), "node count should not be null") + + assert.Falsef(t, obj.Location.IsSpecified(), "location should not be set") + assert.Falsef(t, obj.Location.IsNull(), "location should not be null") + }, + }, + { + name: "complex object: empty config value", + jsonInput: []byte(`{"config":{}}`), + assert: func(t *testing.T, obj ComplexNullable) { + t.Helper() + + assert.Truef(t, obj.Config.IsSpecified(), "config should be set") + assert.Falsef(t, obj.Config.IsNull(), "config should not be null") + + gotConfig, err := obj.Config.Get() + require.NoError(t, err) + + assert.Falsef(t, gotConfig.CPU.IsSpecified(), "cpu should not be set") + assert.Falsef(t, gotConfig.CPU.IsNull(), "cpu should not be null") + + assert.Falsef(t, gotConfig.RAM.IsSpecified(), "ram should not be set") + assert.Falsef(t, gotConfig.RAM.IsNull(), "ram should not be null") + }, + }, + + { + name: "complex object: setting only cpu config value", + jsonInput: []byte(`{"config":{"cpu":"500"}}`), + assert: func(t *testing.T, obj ComplexNullable) { + t.Helper() + + assert.Truef(t, obj.Config.IsSpecified(), "config should be set") + assert.Falsef(t, obj.Config.IsNull(), "config should not be null") + + gotConfig, err := obj.Config.Get() + require.NoError(t, err) + + assert.Truef(t, gotConfig.CPU.IsSpecified(), "cpu should be set") + assert.Falsef(t, gotConfig.CPU.IsNull(), "cpu should not be null") + + assert.Falsef(t, gotConfig.RAM.IsSpecified(), "ram should not be set") + assert.Falsef(t, gotConfig.RAM.IsNull(), "ram should not be null") + }, + }, + + { + name: "complex object: setting only ram config value", + jsonInput: []byte(`{"config":{"ram":"1024"}}`), + assert: func(t *testing.T, obj ComplexNullable) { + t.Helper() + + assert.Truef(t, obj.Config.IsSpecified(), "config should be set") + assert.Falsef(t, obj.Config.IsNull(), "config should not be null") + + gotConfig, err := obj.Config.Get() + require.NoError(t, err) + + assert.Falsef(t, gotConfig.CPU.IsSpecified(), "cpu should not be set") + assert.Falsef(t, gotConfig.CPU.IsNull(), "cpu should not be null") + + assert.Truef(t, gotConfig.RAM.IsSpecified(), "ram should be set") + assert.Falsef(t, gotConfig.RAM.IsNull(), "ram should not be null") + }, + }, + + { + name: "complex object: setting config to null", + jsonInput: []byte(`{"config":null}`), + assert: func(t *testing.T, obj ComplexNullable) { + t.Helper() + + assert.Truef(t, obj.Config.IsSpecified(), "config should be set") + assert.Truef(t, obj.Config.IsNull(), "config should not be null") + }, + }, + + { + name: "complex object: setting only cpu config to null", + jsonInput: []byte(`{"config":{"cpu":null}}`), + assert: func(t *testing.T, obj ComplexNullable) { + t.Helper() + + assert.Truef(t, obj.Config.IsSpecified(), "config should be set") + assert.Falsef(t, obj.Config.IsNull(), "config should not be null") + + gotConfig, err := obj.Config.Get() + require.NoError(t, err) + + assert.Truef(t, gotConfig.CPU.IsSpecified(), "cpu should be set") + assert.Truef(t, gotConfig.CPU.IsNull(), "cpu should be null") + + assert.Falsef(t, gotConfig.RAM.IsSpecified(), "ram should not be set") + assert.Falsef(t, gotConfig.RAM.IsNull(), "ram should not be null") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var obj ComplexNullable + err := json.Unmarshal(tt.jsonInput, &obj) + require.NoError(t, err) + + tt.assert(t, obj) }) } } -// Idempotency tests for nullable and optional +// Tests to validate that repeated unmarshal and marshal should not change +// the JSON for optional and nullable. type StringNullableOptional struct { ID nullable.Nullable[string] `json:"id,omitempty"` Name nullable.Nullable[string] `json:"name,omitempty"` Address nullable.Nullable[string] `json:"address,omitempty"` } -func TestNullableOptionalUnmarshalIdempotency(t *testing.T) { +func TestNullableOptionalUnmarshal(t *testing.T) { var obj1 StringNullableOptional originalJSON1 := []byte(`{}`) + err := json.Unmarshal(originalJSON1, &obj1) require.NoError(t, err) newJSON1, err := json.Marshal(obj1) + require.NoError(t, err) + require.Equal(t, originalJSON1, newJSON1) var obj2 StringNullableOptional originalJSON2 := []byte(`{"id":"12esd412"}`) - err2 := json.Unmarshal(originalJSON2, &obj2) - require.NoError(t, err2) - newJSON2, err2 := json.Marshal(obj2) + + err = json.Unmarshal(originalJSON2, &obj2) + require.NoError(t, err) + newJSON2, err := json.Marshal(obj2) + require.NoError(t, err) + require.Equal(t, originalJSON2, newJSON2) } -func TestNullableOptionalMarshalIdempotency(t *testing.T) { +func TestNullableOptionalMarshal(t *testing.T) { obj1 := StringNullableOptional{ ID: nullable.Nullable[string]{ true: "id-1", @@ -507,8 +652,8 @@ func TestNullableOptionalMarshalIdempotency(t *testing.T) { }, } expectedJSON1 := []byte(`{"id":"id-1","name":"","address":null}`) - gotJSON1, err1 := json.Marshal(obj1) - require.NoError(t, err1) + gotJSON1, err := json.Marshal(obj1) + require.NoError(t, err) require.Equal(t, expectedJSON1, gotJSON1) obj2 := StringNullableOptional{ @@ -520,7 +665,7 @@ func TestNullableOptionalMarshalIdempotency(t *testing.T) { }, } expectedJSON2 := []byte(`{"id":"id-1","address":null}`) - gotJSON2, err2 := json.Marshal(obj2) - require.NoError(t, err2) + gotJSON2, err := json.Marshal(obj2) + require.NoError(t, err) require.Equal(t, expectedJSON2, gotJSON2) }