Files
netbird/e2e/agentnetwork/agent_config_test.go
T
Maycon Santos f29249e7ef [management] Point the agent-config e2e providers at the mock upstream (#7542)
TestAgentConfigAllowlistOfDeclaredModels still pointed its providers at api.openai.com and bedrock-runtime with a dummy key, so every save has been refused with "the provider rejected the credential" and the Agent Network E2E has been red on main since — both subtests, every scheduled run.

Every other suite already uses the mock vLLM upstream (harness.StartVLLM), which answers both the OpenAI (/v1/models) and the Bedrock (/inference-profiles) listing; this test does the same. What it checks — the allowlist advertising the provider's declared ids on GET /api/agent-network/agent-config — never depended on the vendor.
2026-09-15 09:40:52 +02:00

152 lines
6.1 KiB
Go

//go:build e2e
package agentnetwork
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/e2e/harness"
"github.com/netbirdio/netbird/shared/management/http/api"
)
// joinGroup places the PAT's own user into the group so caller-scoped answers
// (GET /api/agent-network/agent-config) see the policies sourced from it, and
// restores the previous auto-groups on cleanup. Self-service updates of one's
// own auto_groups are permitted for every role, so this needs no second user.
func joinGroup(t *testing.T, ctx context.Context, groupID string) {
t.Helper()
me, err := srv.API().Users.Current(ctx)
require.NoError(t, err, "read current user")
before := append([]string(nil), me.AutoGroups...)
_, err = srv.API().Users.Update(ctx, me.Id, api.PutApiUsersUserIdJSONRequestBody{
Role: me.Role,
IsBlocked: me.IsBlocked,
AutoGroups: append(append([]string(nil), before...), groupID),
})
require.NoError(t, err, "add the caller to the policy source group")
t.Cleanup(func() {
_, _ = srv.API().Users.Update(context.Background(), me.Id, api.PutApiUsersUserIdJSONRequestBody{
Role: me.Role,
IsBlocked: me.IsBlocked,
AutoGroups: before,
})
})
}
// configProvider returns the agent-config entry for the named provider, nil
// when the answer does not offer it. The suite shares one account, so other
// tests' fixtures may add unrelated providers to the caller's answer.
func configProvider(cfg api.AgentNetworkAgentConfig, name string) *api.AgentNetworkAgentConfigProvider {
for i := range cfg.Providers {
if cfg.Providers[i].Name == name {
return &cfg.Providers[i]
}
}
return nil
}
// TestAgentConfigAllowlistOfDeclaredModels reproduces the post-#7221 field
// report: a provider carrying a declared model set plus a policy guardrail
// whose allowlist holds those same declared ids must advertise the models on
// GET /api/agent-network/agent-config — the guardrail was built FROM the
// provider's model list (the dashboard's allowlist picker persists the
// declared ids verbatim), so nothing about the setup excludes them.
//
// The plain case passes today. The path-style case (Bedrock; Vertex has the
// same shape) fails: the declared id is compared through the proxy parser's
// canonical form (region prefix and version suffix stripped) while the
// allowlist entry is not, so the raw-vs-raw pair never intersects and the
// caller sees an empty model list. The same one-sided normalization sits in
// policyPermitsModel, so the proxy also denies the model at request time —
// the guardrail meant to allow exactly this model turns it off end to end.
func TestAgentConfigAllowlistOfDeclaredModels(t *testing.T) {
ctx := context.Background()
// Saving a provider makes management verify the credential against the
// upstream, and a real vendor refuses the dummy key and the save with it.
// The providers point at the mock upstream instead: it resolves to a
// private address, which the check declines to dial and treats as
// unverifiable rather than as a failure, so the save goes through. The
// test is about the allowlist, not the upstream.
vllm, err := harness.StartVLLM(ctx, srv)
require.NoError(t, err, "start mock upstream")
t.Cleanup(func() { _ = vllm.Terminate(context.Background()) })
cases := []struct {
name string
catalogID string
declared string
}{
{
name: "plain-declared-id",
catalogID: "openai_api",
declared: "gpt-4o-mini",
},
{
// The operator declares the id AWS issues — region-prefixed
// inference profile with a version suffix — and the allowlist
// picker copies it as-is.
name: "bedrock-declared-id",
catalogID: "bedrock_api",
declared: "eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
grp, err := srv.API().Groups.Create(ctx, api.PostApiGroupsJSONRequestBody{Name: "e2e-agentcfg-" + tc.name})
require.NoError(t, err, "create source group")
t.Cleanup(func() { _ = srv.API().Groups.Delete(context.Background(), grp.Id) })
joinGroup(t, ctx, grp.Id)
providerName := "e2e-agentcfg-" + tc.name
prov, err := srv.CreateProvider(ctx, api.AgentNetworkProviderRequest{
Name: providerName,
ProviderId: tc.catalogID,
UpstreamUrl: vllm.URL,
ApiKey: ptr("sk-dummy-e2e-key"),
Enabled: ptr(true),
Models: &[]api.AgentNetworkProviderModel{{Id: tc.declared, InputPer1k: 0.001, OutputPer1k: 0.002}},
})
require.NoError(t, err, "create provider")
t.Cleanup(func() { _ = srv.DeleteProvider(context.Background(), prov.Id) })
// Allowlist exactly the declared model, the way the dashboard
// builds a guardrail from the provider's model list.
var gr api.AgentNetworkGuardrailRequest
gr.Name = "e2e-agentcfg-" + tc.name
gr.Checks.ModelAllowlist.Enabled = true
gr.Checks.ModelAllowlist.Models = []string{tc.declared}
guard, err := srv.CreateGuardrail(ctx, gr)
require.NoError(t, err, "create guardrail")
t.Cleanup(func() { _ = srv.DeleteGuardrail(context.Background(), guard.Id) })
pol, err := srv.CreatePolicy(ctx, api.AgentNetworkPolicyRequest{
Name: "e2e-agentcfg-" + tc.name,
Enabled: ptr(true),
SourceGroups: []string{grp.Id},
DestinationProviderIds: []string{prov.Id},
GuardrailIds: &[]string{guard.Id},
})
require.NoError(t, err, "create policy")
t.Cleanup(func() { _ = srv.DeletePolicy(context.Background(), pol.Id) })
cfg, err := srv.GetAgentConfig(ctx)
require.NoError(t, err, "read the caller-scoped agent config")
require.True(t, cfg.Configured, "the account endpoint is bootstrapped by TestMain")
entry := configProvider(cfg, providerName)
require.NotNil(t, entry, "the policy authorizes the caller for the provider, so it must be offered")
assert.False(t, entry.AllModelsAllowed, "an allowlist guardrail restricts the provider")
assert.Equal(t, []string{tc.declared}, entry.Models,
"the allowlist holds the provider's own declared id, so that model must be advertised")
})
}
}