package agentnetwork import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/management/internals/modules/agentnetwork/types" "github.com/netbirdio/netbird/management/server/store" ) // This file is the no-mock regression guard for policy limit enforcement. // policyselect_test.go pins the same behavior through a gomock store with // explicit call-sequence expectations — brittle precisely where the upcoming // account-budget work (GC-2) refactors the cap-eval primitive and adds an // account-level gate. These tests drive the REAL sqlite store + REAL // consumption accounting and assert observable behavior (allow / deny / // selection / attribution), not which store methods get called. They must keep // passing unchanged after GC-2 lands, which is what proves "current behavior is // not changed." const realSelectAccount = "acc-realselect-1" // newRealSelectorMgr builds a managerImpl backed by a real sqlite test store. func newRealSelectorMgr(t *testing.T) (*managerImpl, store.Store) { t.Helper() ctx := context.Background() s, cleanup, err := store.NewTestStoreFromSQL(ctx, "", t.TempDir()) require.NoError(t, err, "real sqlite test store must come up") t.Cleanup(cleanup) return &managerImpl{store: s}, s } // TestSelectPolicy_RealStore_NoApplicablePolicies pins the pass-through: // nothing targets the (provider, groups) combination, so the selector allows // without attribution or consumption tracking. func TestSelectPolicy_RealStore_NoApplicablePolicies(t *testing.T) { mgr, _ := newRealSelectorMgr(t) res, err := mgr.SelectPolicyForRequest(context.Background(), PolicySelectionInput{ AccountID: realSelectAccount, UserID: "user-1", GroupIDs: []string{"grp-x"}, ProviderID: "prov-1", }) require.NoError(t, err) assert.True(t, res.Allow, "no applicable policy must pass through as allow") assert.Empty(t, res.SelectedPolicyID, "no selection when nothing applies") } // TestSelectPolicy_RealStore_AllowAndLowestGroupAttribution pins the v1 // attribution rule (lowest intersecting group by string sort) through the // real store, with a fresh (zero) consumption row. func TestSelectPolicy_RealStore_AllowAndLowestGroupAttribution(t *testing.T) { mgr, s := newRealSelectorMgr(t) ctx := context.Background() p := capPolicy("pol-A", realSelectAccount, []string{"grp-zz", "grp-aa", "grp-mm"}, "prov-1", 10_000, 86_400) require.NoError(t, s.SaveAgentNetworkPolicy(ctx, p)) res, err := mgr.SelectPolicyForRequest(ctx, PolicySelectionInput{ AccountID: realSelectAccount, UserID: "user-1", GroupIDs: []string{"grp-zz", "grp-aa", "grp-mm"}, ProviderID: "prov-1", }) require.NoError(t, err) assert.True(t, res.Allow, "fresh state under cap must allow") assert.Equal(t, "pol-A", res.SelectedPolicyID, "only applicable policy must be selected") assert.Equal(t, "grp-aa", res.AttributionGroupID, "lowest-by-sort intersecting group must win") assert.Equal(t, int64(86_400), res.WindowSeconds, "selected policy's window must be returned") } // TestSelectPolicy_RealStore_LargerPoolWins_FallsThroughWhenExhausted pins the // core selection behavior end to end. The two policies bind DISTINCT groups so // they read separate counters — the only shape where fall-through actually // yields headroom (policies on the same group share one counter, as // policyselect_test.go notes). Larger pool wins fresh; after real consumption // drains the larger group, selection falls through to the smaller; once both // counters are exhausted the request is denied. func TestSelectPolicy_RealStore_LargerPoolWins_FallsThroughWhenExhausted(t *testing.T) { mgr, s := newRealSelectorMgr(t) ctx := context.Background() tight := capPolicy("pol-tight", realSelectAccount, []string{"grp-tight"}, "prov-1", 100, 86_400) tight.CreatedAt = time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) wide := capPolicy("pol-wide", realSelectAccount, []string{"grp-wide"}, "prov-1", 10_000, 86_400) wide.CreatedAt = time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC) require.NoError(t, s.SaveAgentNetworkPolicy(ctx, tight)) require.NoError(t, s.SaveAgentNetworkPolicy(ctx, wide)) // Caller is in both groups, so both policies apply with independent counters. in := PolicySelectionInput{ AccountID: realSelectAccount, UserID: "user-1", GroupIDs: []string{"grp-tight", "grp-wide"}, ProviderID: "prov-1", } // Fresh: larger pool wins. res, err := mgr.SelectPolicyForRequest(ctx, in) require.NoError(t, err) assert.Equal(t, "pol-wide", res.SelectedPolicyID, "larger pool drains first") // Drain only the wide group's counter to its cap. require.NoError(t, mgr.RecordConsumption(ctx, realSelectAccount, types.DimensionGroup, "grp-wide", 86_400, 10_000, 0, 0)) // Wide exhausted, tight's separate counter is fresh → fall through to tight. res, err = mgr.SelectPolicyForRequest(ctx, in) require.NoError(t, err) assert.True(t, res.Allow, "tight pool has its own untouched counter") assert.Equal(t, "pol-tight", res.SelectedPolicyID, "selection falls through to the smaller pool once the larger is exhausted") // Drain the tight group's counter too → both exhausted → deny. require.NoError(t, mgr.RecordConsumption(ctx, realSelectAccount, types.DimensionGroup, "grp-tight", 86_400, 100, 0, 0)) res, err = mgr.SelectPolicyForRequest(ctx, in) require.NoError(t, err) assert.False(t, res.Allow, "both group counters exhausted must deny") assert.Equal(t, denyCodeTokenCapExceeded, res.DenyCode, "deny code names the offending cap kind") } // TestSelectPolicy_RealStore_BudgetCapDenies pins budget (USD) enforcement // through the real store: once recorded cost reaches the cap, deny. func TestSelectPolicy_RealStore_BudgetCapDenies(t *testing.T) { mgr, s := newRealSelectorMgr(t) ctx := context.Background() p := &types.Policy{ ID: "pol-budget", AccountID: realSelectAccount, Enabled: true, SourceGroups: []string{"grp-eng"}, DestinationProviderIDs: []string{"prov-1"}, Limits: types.PolicyLimits{ BudgetLimit: types.PolicyBudgetLimit{ Enabled: true, GroupCapUsd: 5.0, WindowSeconds: 86_400, }, }, CreatedAt: time.Now().UTC(), } require.NoError(t, s.SaveAgentNetworkPolicy(ctx, p)) in := PolicySelectionInput{ AccountID: realSelectAccount, UserID: "user-1", GroupIDs: []string{"grp-eng"}, ProviderID: "prov-1", } res, err := mgr.SelectPolicyForRequest(ctx, in) require.NoError(t, err) assert.True(t, res.Allow, "fresh budget must allow") require.NoError(t, mgr.RecordConsumption(ctx, realSelectAccount, types.DimensionGroup, "grp-eng", 86_400, 0, 0, 5.0)) res, err = mgr.SelectPolicyForRequest(ctx, in) require.NoError(t, err) assert.False(t, res.Allow, "cost at the cap must deny") assert.Equal(t, denyCodeBudgetCapExceeded, res.DenyCode, "budget deny code must be surfaced") } // TestSelectPolicy_RealStore_GroupCounterSharedAcrossPolicies pins that two // policies on the same group+window read one shared consumption counter: usage // recorded once is visible to both, so exhausting the group budget denies // regardless of which policy would attribute. func TestSelectPolicy_RealStore_GroupCounterSharedAcrossPolicies(t *testing.T) { mgr, s := newRealSelectorMgr(t) ctx := context.Background() a := capPolicy("pol-a", realSelectAccount, []string{"grp-eng"}, "prov-1", 1_000, 86_400) b := capPolicy("pol-b", realSelectAccount, []string{"grp-eng"}, "prov-1", 1_000, 86_400) require.NoError(t, s.SaveAgentNetworkPolicy(ctx, a)) require.NoError(t, s.SaveAgentNetworkPolicy(ctx, b)) in := PolicySelectionInput{ AccountID: realSelectAccount, UserID: "user-1", GroupIDs: []string{"grp-eng"}, ProviderID: "prov-1", } require.NoError(t, mgr.RecordConsumption(ctx, realSelectAccount, types.DimensionGroup, "grp-eng", 86_400, 1_000, 0, 0)) res, err := mgr.SelectPolicyForRequest(ctx, in) require.NoError(t, err) assert.False(t, res.Allow, "shared group counter at cap denies both equal policies") assert.Equal(t, denyCodeTokenCapExceeded, res.DenyCode, "token deny code on the shared counter") } // TestSelectPolicy_RealStore_DisabledPolicyIgnored pins that a disabled policy // is invisible to selection even when it otherwise matches. func TestSelectPolicy_RealStore_DisabledPolicyIgnored(t *testing.T) { mgr, s := newRealSelectorMgr(t) ctx := context.Background() p := capPolicy("pol-disabled", realSelectAccount, []string{"grp-eng"}, "prov-1", 10_000, 86_400) p.Enabled = false require.NoError(t, s.SaveAgentNetworkPolicy(ctx, p)) res, err := mgr.SelectPolicyForRequest(ctx, PolicySelectionInput{ AccountID: realSelectAccount, UserID: "user-1", GroupIDs: []string{"grp-eng"}, ProviderID: "prov-1", }) require.NoError(t, err) assert.True(t, res.Allow, "no enabled policy applies → pass-through allow") assert.Empty(t, res.SelectedPolicyID, "disabled policy must not be selected") }