mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** For #39344
# Details
This PR builds on the previous PR
(https://github.com/fleetdm/fleet/pull/39847) which added `renameto`
tags to certain API parameters to mark them as deprecated. How this is
used:
### In requests
* When decoding requests, log a warning if a `json` or `query` param is
used that has a `renameto` tag, e.g. if a `team_id` param is sent but
the related struct has `renameto:"fleet_id"` in it.
* If the `renamedto` version (e.g. `fleet_id`) is sent in the request,
rewrite it to the deprecated name so that it can be unmarshalled into
the struct
* If both versions are sent (e.g. `team_id` AND `fleet_id`), throw an
error and quit
* URLs with deprecated terms have new aliases using `WithAltPaths` --
warning on using old URLSs a TODO that will be handled in a subsequent
PR.
### In responses
* Output _both_ the deprecated and new names for fields that have
`renameto` tags, so that we don't break existing workflows expecting the
old keys. Uses a shared `DuplicateJSONKeys` to do the duplication.
* Most API responses are handled in `EncodeCommonResponse`. Exceptions
are activities, failing policy webhooks and the streaming "list hosts"
endpoints which call the function directly.
### In fleetctl
* Similar to requests, log warnings when deprecated keys are used and
rewrite the new keys internally so that they can be unmarshalled.
* For `fleetctl get` and `fleetctl generate-gitops`, _only_ output the
new names
* The set of keys to replace is hardcoded in `fleetctl` rather than
being dynamically generated as it is for API endpoints. Given the
mixture of typed and untyped data and the level of nesting, dynamic map
generation was very fragile and error-prone.
### Performance considerations
* The biggest performance hit is the addition of the JSON key rewriter
to the request pipeline. The rewriter buffers the entire request into
memory before eventually passing it to the decoder than unmarshals the
data into structs. I tried implementing this as a true streaming
rewriter but encountered issues where the request would hang if the
downstream reader (the decoder) encountered any errors. It's possible we
could implement this in a streaming fashion if we replace our [current
request
decoder](da43bf8371/server/service/endpoint_utils.go (L108))
with the v2 version, which is a bigger change requiring more thoughtful
discussion in the engineering team. As it stands, memory usage for
requests with deprecated fields will double while the request is being
decoded.
* The "alias rules" used to determine the old and new key names are
cached per struct type and for most endpoints are generated on server
start, so no performance impact is expected.
* Some `fleetctl` commands may have an extra unmarshal/marshal step but
as these are user-initiated and not performed in tight loops, the impact
should be minimal.
### TODO
* Log deprecation warnings when old URLs like "/fleet/teams" are used
* Update API fields that the front-end uses to avoid deprecation
warnings
* Update `fleetctl apply` to accept/return `kind: fleet` rather than
`kind: team`
* Find/update any fleet server config vars with old language
* Update all error messages that use old language
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [X] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
## Testing
- [X] Added/updated automated tests
- [X] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [X] QA'd all new/changed functionality manually
* Clicking around the front-end, no broken pages due to request
ingestion errors or bad responses
* Looking in network tab to verify that responses have both the old and
new keys
* Running `fleetctl generate-gitops` and verifying that the output looks
correct and can be ingested by `fleetctl gitops`
* Running `fleetctl get` and `fleetctl apply`
---------
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
470 lines
15 KiB
Go
470 lines
15 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"])
|
|
}
|
|
}
|
|
|
|
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"`
|
|
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"`
|
|
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"`
|
|
}
|
|
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")
|
|
}
|