Files
fleet/server/platform/endpointer/json_key_rewriter_test.go
Lucas Manuel Rodriguez 9032883b47 Fix fleetctl get fleets to use source of truth (DB) for software (#46480)
Resolves #44970 (1/2).

---

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* `fleetctl get fleets` / `get teams` now display software and setup
experience from authoritative software endpoints.
* Preserve literal setup_experience fields (avoid erroneous macos_setup
renames) when applying and when transmitting JSON for software entries.
* **Tests**
* Added regression tests and test helpers to ensure
software/setup_experience are sourced correctly and to prevent nil
panics in related tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-01 11:41:34 -03:00

596 lines
21 KiB
Go

package endpointer
import (
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestJSONKeyRewriteReader_OldKeyPassThrough(t *testing.T) {
// Old (deprecated) key should pass through as-is and be tracked.
input := `{"team_id": 42, "name": "hello"}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(42), result["team_id"])
assert.Equal(t, "hello", result["name"])
assert.Nil(t, result["fleet_id"], "new key should not appear in output")
// Verify deprecated key was tracked.
assert.Equal(t, []string{"team_id"}, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_NewKeyRewritten(t *testing.T) {
// New key should be rewritten to old key for struct deserialization.
input := `{"fleet_id": 42, "name": "hello"}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(42), result["team_id"])
assert.Nil(t, result["fleet_id"], "new key should be rewritten to old")
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_NoRewriteNeeded(t *testing.T) {
// Unrelated keys should pass through unchanged.
input := `{"other_field": 42, "name": "hello"}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(42), result["other_field"])
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_AliasConflict(t *testing.T) {
input := `{"team_id": 42, "fleet_id": 99}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
_, err := io.ReadAll(r)
require.Error(t, err)
var ace *AliasConflictError
require.True(t, errors.As(err, &ace))
assert.Equal(t, "team_id", ace.Old)
assert.Equal(t, "fleet_id", ace.New)
}
func TestJSONKeyRewriteReader_AliasConflictNewThenOld(t *testing.T) {
// New key first, then deprecated key.
input := `{"fleet_id": 99, "team_id": 42}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
_, err := io.ReadAll(r)
require.Error(t, err)
var ace *AliasConflictError
require.True(t, errors.As(err, &ace))
}
func TestJSONKeyRewriteReader_NestedObjects(t *testing.T) {
input := `{"outer": {"team_id": 1}, "team_id": 2}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
// Old keys should pass through as-is.
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(2), result["team_id"])
inner := result["outer"].(map[string]any)
assert.Equal(t, float64(1), inner["team_id"])
assert.Contains(t, r.UsedDeprecatedKeys(), "team_id")
}
func TestJSONKeyRewriteReader_NestedNewKeys(t *testing.T) {
input := `{"outer": {"fleet_id": 1}, "fleet_id": 2}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
// New keys should be rewritten to old keys.
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(2), result["team_id"])
inner := result["outer"].(map[string]any)
assert.Equal(t, float64(1), inner["team_id"])
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_NestedConflictDoesNotAffectOuter(t *testing.T) {
// Conflict in nested object should be detected, even though outer is fine.
input := `{"name": "ok", "inner": {"team_id": 1, "fleet_id": 2}}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
_, err := io.ReadAll(r)
require.Error(t, err)
var ace *AliasConflictError
require.True(t, errors.As(err, &ace))
}
func TestJSONKeyRewriteReader_NoConflictAcrossScopes(t *testing.T) {
// team_id in outer, fleet_id in inner — no conflict (different scopes).
input := `{"team_id": 1, "inner": {"fleet_id": 2}}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(1), result["team_id"])
inner := result["inner"].(map[string]any)
assert.Equal(t, float64(2), inner["team_id"]) // fleet_id rewritten to team_id
}
func TestJSONKeyRewriteReader_StringValuesNotRewritten(t *testing.T) {
// "team_id" as a string value (not a key) should NOT be rewritten.
input := `{"name": "team_id", "description": "the team_id field"}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
// String values should not be rewritten, only keys.
assert.Equal(t, "team_id", result["name"], "string value should not be rewritten")
assert.Equal(t, "the team_id field", result["description"])
// Make sure it didn't accidentally transform the team_id string value into a new fleet_id key.
assert.Empty(t, result["fleet_id"], "new key should not appear in output")
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_ArrayValues(t *testing.T) {
input := `{"team_id": [1, 2, 3], "items": ["a", "b"]}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.NotNil(t, result["team_id"])
assert.Contains(t, r.UsedDeprecatedKeys(), "team_id")
}
func TestJSONKeyRewriteReader_ArrayOfObjects(t *testing.T) {
input := `{"items": [{"team_id": 1}, {"team_id": 2}]}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
items := result["items"].([]any)
for _, item := range items {
obj := item.(map[string]any)
assert.NotNil(t, obj["team_id"])
assert.Nil(t, obj["fleet_id"])
}
}
// TestJSONKeyRewriteReader_SoftwareSubtreeSkipsRules verifies that keys inside
// the `software` subtree are not subject to rename rules. The literal
// `setup_experience` install flag on SoftwarePackageSpec / TeamSpecAppStoreApp
// / MaintainedAppSpec items collides with the `macos_setup`↔`setup_experience`
// rename on the MDM section, and must be passed through untouched.
// Regression test for https://github.com/fleetdm/fleet/issues/44970.
func TestJSONKeyRewriteReader_SoftwareSubtreeSkipsRules(t *testing.T) {
input := `{
"setup_experience": {"enable_end_user_authentication": true},
"software": {
"packages": [
{"url": "http://foo", "setup_experience": true},
{"url": "http://bar", "setup_experience": false}
],
"app_store_apps": [
{"app_store_id": "1", "setup_experience": null}
],
"fleet_maintained_apps": [
{"slug": "foo", "setup_experience": true}
]
}
}`
rules := []AliasRule{{OldKey: "macos_setup", NewKey: "setup_experience"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
// Top-level `setup_experience` (the object) is rewritten to `macos_setup`.
assert.NotNil(t, result["macos_setup"], "top-level container key must be rewritten")
_, hasNewAtRoot := result["setup_experience"]
assert.False(t, hasNewAtRoot, "new key should have been rewritten at the root")
// Literal `setup_experience` flags inside software entries must NOT have
// been rewritten to `macos_setup`.
sw := result["software"].(map[string]any)
pkgs := sw["packages"].([]any)
assert.Equal(t, true, pkgs[0].(map[string]any)["setup_experience"])
assert.Equal(t, false, pkgs[1].(map[string]any)["setup_experience"])
_, hasMacOSSetupOnPkg := pkgs[0].(map[string]any)["macos_setup"]
assert.False(t, hasMacOSSetupOnPkg, "literal setup_experience inside software must not be rewritten")
apps := sw["app_store_apps"].([]any)
assert.Nil(t, apps[0].(map[string]any)["setup_experience"])
_, hasMacOSSetupOnApp := apps[0].(map[string]any)["macos_setup"]
assert.False(t, hasMacOSSetupOnApp, "null setup_experience inside software must not be rewritten")
fmas := sw["fleet_maintained_apps"].([]any)
assert.Equal(t, true, fmas[0].(map[string]any)["setup_experience"])
_, hasMacOSSetupOnFMA := fmas[0].(map[string]any)["macos_setup"]
assert.False(t, hasMacOSSetupOnFMA, "literal setup_experience on FMA must not be rewritten")
}
// TestRewriteOldToNewKeys_SoftwareSubtreeSkipsRules verifies the same software-
// scope skip in the reverse direction (old→new). A client posting a YAML with
// `setup_experience: true` on software items must not have those flags clobbered
// to `macos_setup` during client-side normalization.
func TestRewriteOldToNewKeys_SoftwareSubtreeSkipsRules(t *testing.T) {
input := `{
"macos_setup": {"enable_end_user_authentication": true},
"software": {
"packages": [{"url": "http://foo", "setup_experience": true}],
"app_store_apps": [{"app_store_id": "1", "setup_experience": true}],
"fleet_maintained_apps": [{"slug": "foo", "setup_experience": true}]
}
}`
rules := []AliasRule{{OldKey: "macos_setup", NewKey: "setup_experience"}}
out, err := RewriteOldToNewKeys([]byte(input), rules)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
// Top-level old `macos_setup` is rewritten to new `setup_experience`.
assert.NotNil(t, result["setup_experience"], "top-level old key must be rewritten to new")
_, hasOldAtRoot := result["macos_setup"]
assert.False(t, hasOldAtRoot, "old key should have been rewritten at the root")
// Literal `setup_experience` flags inside software entries must remain.
sw := result["software"].(map[string]any)
for _, key := range []string{"packages", "app_store_apps", "fleet_maintained_apps"} {
items := sw[key].([]any)
first := items[0].(map[string]any)
assert.Equal(t, true, first["setup_experience"], "literal flag on %s must be preserved", key)
_, hasOld := first["macos_setup"]
assert.False(t, hasOld, "literal setup_experience on %s must not be renamed to macos_setup", key)
}
}
func TestJSONKeyRewriteReader_MultipleRules(t *testing.T) {
input := `{"team_id": 1, "team_name": "Engineering"}`
rules := []AliasRule{
{OldKey: "team_id", NewKey: "fleet_id"},
{OldKey: "team_name", NewKey: "fleet_name"},
}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(1), result["team_id"])
assert.Equal(t, "Engineering", result["team_name"])
assert.Nil(t, result["fleet_id"])
assert.Nil(t, result["fleet_name"])
deprecated := r.UsedDeprecatedKeys()
assert.Len(t, deprecated, 2)
assert.Contains(t, deprecated, "team_id")
assert.Contains(t, deprecated, "team_name")
}
func TestJSONKeyRewriteReader_MultipleRulesNewKeys(t *testing.T) {
// New keys should be rewritten to old keys.
input := `{"fleet_id": 1, "fleet_name": "Engineering"}`
rules := []AliasRule{
{OldKey: "team_id", NewKey: "fleet_id"},
{OldKey: "team_name", NewKey: "fleet_name"},
}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(1), result["team_id"])
assert.Equal(t, "Engineering", result["team_name"])
assert.Nil(t, result["fleet_id"])
assert.Nil(t, result["fleet_name"])
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_EmptyObject(t *testing.T) {
input := `{}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
assert.JSONEq(t, `{}`, string(out))
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_NullValues(t *testing.T) {
input := `{"team_id": null}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Contains(t, result, "team_id")
assert.Nil(t, result["team_id"])
}
func TestJSONKeyRewriteReader_BooleanValues(t *testing.T) {
input := `{"team_id": true}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, true, result["team_id"])
}
func TestJSONKeyRewriteReader_NoRules(t *testing.T) {
input := `{"team_id": 42}`
var rules []AliasRule
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
// With no rules, output should be identical to input.
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(42), result["team_id"])
assert.Empty(t, r.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_LargePayload(t *testing.T) {
// Build a large JSON payload that exceeds the internal buffer size (4096 bytes).
var sb strings.Builder
sb.WriteString(`{"team_id": 1`)
for i := range 500 {
sb.WriteString(fmt.Sprintf(`, "field_%04d": "value"`, i))
}
sb.WriteString(`}`)
input := sb.String()
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(1), result["team_id"])
assert.Nil(t, result["fleet_id"])
assert.Contains(t, r.UsedDeprecatedKeys(), "team_id")
}
func TestJSONKeyRewriteReader_WithJSONDecoderOldKey(t *testing.T) {
// Simulate the real usage: json.NewDecoder reading from the rewriter
// with old (deprecated) key in the request. The struct uses old key names.
input := `{"team_id": 42, "name": "test"}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
rewriter := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
type request struct {
TeamID int `json:"team_id"` //nolint:apiparamcheck // rename handled centrally by spec.DeprecatedGitOpsKeyMappings
Name string `json:"name"`
}
var req request
err := json.NewDecoder(rewriter).Decode(&req)
require.NoError(t, err)
assert.Equal(t, 42, req.TeamID)
assert.Equal(t, "test", req.Name)
assert.Contains(t, rewriter.UsedDeprecatedKeys(), "team_id")
}
func TestJSONKeyRewriteReader_WithJSONDecoderNewKey(t *testing.T) {
// Simulate the real usage: json.NewDecoder reading from the rewriter
// with new key in the request. Should be rewritten to old key.
input := `{"fleet_id": 42, "name": "test"}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
rewriter := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
type request struct {
TeamID int `json:"team_id"` //nolint:apiparamcheck // rename handled centrally by spec.DeprecatedGitOpsKeyMappings
Name string `json:"name"`
}
var req request
err := json.NewDecoder(rewriter).Decode(&req)
require.NoError(t, err)
assert.Equal(t, 42, req.TeamID)
assert.Equal(t, "test", req.Name)
assert.Empty(t, rewriter.UsedDeprecatedKeys())
}
func TestJSONKeyRewriteReader_AliasConflictWithJSONDecoder(t *testing.T) {
input := `{"team_id": 42, "fleet_id": 99}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
rewriter := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
type request struct {
TeamID int `json:"team_id"` //nolint:apiparamcheck // rename handled centrally by spec.DeprecatedGitOpsKeyMappings
}
var req request
err := json.NewDecoder(rewriter).Decode(&req)
require.Error(t, err)
var ace *AliasConflictError
require.True(t, errors.As(err, &ace))
assert.Equal(t, "team_id", ace.Old)
assert.Equal(t, "fleet_id", ace.New)
}
func TestJSONKeyRewriteReader_DeeplyNestedObjectsOldKeys(t *testing.T) {
input := `{"a": {"b": {"c": {"team_id": 99}}}}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
inner := result["a"].(map[string]any)["b"].(map[string]any)["c"].(map[string]any)
assert.Equal(t, float64(99), inner["team_id"])
assert.Contains(t, r.UsedDeprecatedKeys(), "team_id")
}
func TestJSONKeyRewriteReader_DeeplyNestedObjectsNewKeys(t *testing.T) {
input := `{"a": {"b": {"c": {"fleet_id": 99}}}}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
inner := result["a"].(map[string]any)["b"].(map[string]any)["c"].(map[string]any)
assert.Equal(t, float64(99), inner["team_id"])
}
func TestJSONKeyRewriteReader_TopLevelArrayOldKeys(t *testing.T) {
input := `[{"team_id": 1}, {"team_id": 2}]`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result []map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Len(t, result, 2)
assert.Equal(t, float64(1), result[0]["team_id"])
assert.Equal(t, float64(2), result[1]["team_id"])
assert.Contains(t, r.UsedDeprecatedKeys(), "team_id")
}
func TestJSONKeyRewriteReader_TopLevelArrayNewKeys(t *testing.T) {
input := `[{"fleet_id": 1}, {"fleet_id": 2}]`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result []map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Len(t, result, 2)
assert.Equal(t, float64(1), result[0]["team_id"])
assert.Equal(t, float64(2), result[1]["team_id"])
}
func TestJSONKeyRewriteReader_NestedObjectStringValue(t *testing.T) {
// Object as value with keys that need tracking.
input := `{"config": {"team_id": 5, "enabled": true}}`
rules := []AliasRule{{OldKey: "team_id", NewKey: "fleet_id"}}
r := NewJSONKeyRewriteReader(strings.NewReader(input), rules)
out, err := io.ReadAll(r)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
config := result["config"].(map[string]any)
assert.Equal(t, float64(5), config["team_id"])
assert.Equal(t, true, config["enabled"])
assert.Equal(t, []string{"team_id"}, r.UsedDeprecatedKeys())
}
func TestAliasConflictError_ErrorMessage(t *testing.T) {
err := &AliasConflictError{Old: "team_id", New: "fleet_id"}
assert.Contains(t, err.Error(), "team_id")
assert.Contains(t, err.Error(), "fleet_id")
}
func TestRewriteOldToNewKeys(t *testing.T) {
rules := []AliasRule{
{OldKey: "team_id", NewKey: "fleet_id"},
{OldKey: "team", NewKey: "fleet"},
{OldKey: "custom_settings", NewKey: "configuration_profiles"},
}
t.Run("rewrites old keys to new", func(t *testing.T) {
input := `{"team_id":42,"name":"hello","team":"engineering"}`
out, err := RewriteOldToNewKeys([]byte(input), rules)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(42), result["fleet_id"])
assert.Equal(t, "engineering", result["fleet"])
assert.Equal(t, "hello", result["name"])
assert.Nil(t, result["team_id"])
assert.Nil(t, result["team"])
})
t.Run("new keys pass through unchanged", func(t *testing.T) {
input := `{"fleet_id":42}`
out, err := RewriteOldToNewKeys([]byte(input), rules)
require.NoError(t, err)
var result map[string]any
require.NoError(t, json.Unmarshal(out, &result))
assert.Equal(t, float64(42), result["fleet_id"])
})
}