Files
fleet/server/platform/endpointer/extract_alias_rules_test.go
Jordan Montgomery 356caea6fd 42508 Rename abm to ab in API (#46657)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #42508 

Renames abm/apple_business_manager to ab/apple_business in API and
fleetctl. Uses existing renameto logic with a slight twist: added
"inline" option to handle cases particularly where a single object tree
has renames in multiple versions so that we don't break backwards
compatibiility since the default behavior when you have multi-level
renames is a new/old split at the top level

# 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), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## 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

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

* **New Features**
* Canonical Apple Business (AB) API endpoints and CLI:
/api/v1/fleet/ab_tokens, /api/v1/fleet/mdm/apple/ab_public_key, plus new
fleetctl get mdm-ab and fleetctl generate mdm-ab
  * New GitOps/config key: mdm.apple_business
* Admin UI updated to show Apple Business tokens with fleet-based
associations and updated labels

* **Deprecations**
* Legacy ABM endpoints, CLI aliases, and config keys remain supported
but emit deprecation warnings pointing to the new AB equivalents
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-03 14:58:17 -04:00

220 lines
5.9 KiB
Go

package endpointer
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type extractAliasRulesSuite struct {
suite.Suite
}
func TestExtractAliasRules(t *testing.T) {
suite.Run(t, new(extractAliasRulesSuite))
}
// SetupTest runs before every Test* method, clearing the global cache.
func (s *extractAliasRulesSuite) SetupTest() {
aliasRulesCache.Range(func(key, _ any) bool {
aliasRulesCache.Delete(key)
return true
})
}
func (s *extractAliasRulesSuite) TestNilInput() {
rules := ExtractAliasRules(nil)
require.Nil(s.T(), rules)
}
func (s *extractAliasRulesSuite) TestNonStructInput() {
rules := ExtractAliasRules("hello")
require.Nil(s.T(), rules)
rules = ExtractAliasRules(42)
require.Nil(s.T(), rules)
}
func (s *extractAliasRulesSuite) TestStructWithNoRenametoTags() {
type plain struct {
Name string `json:"name"`
Age int `json:"age"`
}
rules := ExtractAliasRules(plain{})
require.Empty(s.T(), rules)
}
func (s *extractAliasRulesSuite) TestSingleRenametoTag() {
type singleAlias struct {
TeamID uint `json:"team_id" renameto:"group_id"`
}
rules := ExtractAliasRules(singleAlias{})
require.Equal(s.T(), []AliasRule{{OldKey: "team_id", NewKey: "group_id"}}, rules)
}
func (s *extractAliasRulesSuite) TestRenametoInlineOption() {
type inlineAlias struct {
Tokens []string `json:"abm_tokens" renameto:"ab_tokens,inline"`
TeamID uint `json:"team_id" renameto:"fleet_id"`
}
rules := ExtractAliasRules(inlineAlias{})
s.Require().Equal([]AliasRule{
{OldKey: "abm_tokens", NewKey: "ab_tokens", Inline: true},
{OldKey: "team_id", NewKey: "fleet_id"},
}, rules)
}
func (s *extractAliasRulesSuite) TestMultipleRenametoTags() {
type multiAlias struct {
TeamID uint `json:"team_id" renameto:"group_id"`
TeamName string `json:"team_name" renameto:"group_name"`
Other string `json:"other"`
}
rules := ExtractAliasRules(multiAlias{})
require.Equal(s.T(), []AliasRule{
{OldKey: "team_id", NewKey: "group_id"},
{OldKey: "team_name", NewKey: "group_name"},
}, rules)
}
func (s *extractAliasRulesSuite) TestJsonTagWithOmitempty() {
type omitemptyAlias struct {
TeamID uint `json:"team_id,omitempty" renameto:"group_id"`
}
rules := ExtractAliasRules(omitemptyAlias{})
require.Equal(s.T(), []AliasRule{{OldKey: "team_id", NewKey: "group_id"}}, rules)
}
func (s *extractAliasRulesSuite) TestJsonTagDashIsSkipped() {
type dashJSON struct {
Secret string `json:"-" renameto:"new_secret"`
}
rules := ExtractAliasRules(dashJSON{})
require.Empty(s.T(), rules)
}
func (s *extractAliasRulesSuite) TestNoJsonTagWithRenametoIsSkipped() {
type noJSON struct {
Field string `renameto:"new_field"`
}
rules := ExtractAliasRules(noJSON{})
require.Empty(s.T(), rules)
}
func (s *extractAliasRulesSuite) TestEmptyRenametoIsSkipped() {
type emptyRenameTo struct {
Field string `json:"field" renameto:""`
}
rules := ExtractAliasRules(emptyRenameTo{})
require.Empty(s.T(), rules)
}
func (s *extractAliasRulesSuite) TestNestedStruct() {
type inner struct {
InnerField string `json:"inner_field" renameto:"new_inner"`
}
type outer struct {
OuterField string `json:"outer_field" renameto:"new_outer"`
Nested inner
}
rules := ExtractAliasRules(outer{})
require.Equal(s.T(), []AliasRule{
{OldKey: "outer_field", NewKey: "new_outer"},
{OldKey: "inner_field", NewKey: "new_inner"},
}, rules)
}
func (s *extractAliasRulesSuite) TestDeeplyNestedStructs() {
type level2 struct {
Deep string `json:"deep" renameto:"new_deep"`
}
type level1 struct {
Mid string `json:"mid" renameto:"new_mid"`
Nested level2
}
type level0 struct {
Top string `json:"top" renameto:"new_top"`
Nested level1
}
rules := ExtractAliasRules(level0{})
require.Equal(s.T(), []AliasRule{
{OldKey: "top", NewKey: "new_top"},
{OldKey: "mid", NewKey: "new_mid"},
{OldKey: "deep", NewKey: "new_deep"},
}, rules)
}
func (s *extractAliasRulesSuite) TestDeduplicationAcrossNestedStructs() {
type shared struct {
TeamID uint `json:"team_id" renameto:"group_id"`
}
type parent struct {
TeamID uint `json:"team_id" renameto:"group_id"`
Child shared
}
rules := ExtractAliasRules(parent{})
// The same OldKey→NewKey pair should appear only once.
require.Equal(s.T(), []AliasRule{{OldKey: "team_id", NewKey: "group_id"}}, rules)
}
func (s *extractAliasRulesSuite) TestPointerToStructInput() {
type ptrInput struct {
Field string `json:"field" renameto:"new_field"`
}
rules := ExtractAliasRules(&ptrInput{})
require.Equal(s.T(), []AliasRule{{OldKey: "field", NewKey: "new_field"}}, rules)
}
func (s *extractAliasRulesSuite) TestNestedThroughPointerField() {
type pointed struct {
Inner string `json:"inner" renameto:"new_inner"`
}
type wrapper struct {
Ptr *pointed
}
rules := ExtractAliasRules(wrapper{})
require.Equal(s.T(), []AliasRule{{OldKey: "inner", NewKey: "new_inner"}}, rules)
}
func (s *extractAliasRulesSuite) TestNestedThroughSliceField() {
type elem struct {
Val string `json:"val" renameto:"new_val"`
}
type sliceWrapper struct {
Items []elem
}
rules := ExtractAliasRules(sliceWrapper{})
require.Equal(s.T(), []AliasRule{{OldKey: "val", NewKey: "new_val"}}, rules)
}
func (s *extractAliasRulesSuite) TestNestedThroughMapField() {
type mapVal struct {
Key string `json:"key" renameto:"new_key"`
}
type mapWrapper struct {
Data map[string]mapVal
}
rules := ExtractAliasRules(mapWrapper{})
require.Equal(s.T(), []AliasRule{{OldKey: "key", NewKey: "new_key"}}, rules)
}
func (s *extractAliasRulesSuite) TestDeduplicationSameStructViaMultiplePaths() {
type common struct {
ID string `json:"old_id" renameto:"new_id"`
}
type branch1 struct {
C common
}
type branch2 struct {
C common
}
type root struct {
B1 branch1
B2 branch2
}
rules := ExtractAliasRules(root{})
// common's rule should appear only once even though reachable via two paths.
require.Equal(s.T(), []AliasRule{{OldKey: "old_id", NewKey: "new_id"}}, rules)
}