mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-17 03:59:56 +00:00
Port the API-driven agent-network scenarios from the bash suites to Go, sharing one combined server per package run (TestMain) with each test owning its resource cleanup. Drives the /api/agent-network/* endpoints through the shared REST client's NewRequest primitive with the generated api types. Scenarios: - provider lifecycle (create/get/list/delete + 404 after delete) - provider validation (missing api_key, unknown catalog id → 4xx) - settings collection-toggle round-trip with cluster/subdomain immutability - policy window floor (reject <60s enabled limit, accept at 60s) - consumption read endpoint returns an array All deterministic and dependency-free (dummy provider keys; no upstream calls), so they run headless in CI.
170 lines
5.9 KiB
Go
170 lines
5.9 KiB
Go
//go:build e2e
|
|
|
|
package agentnetwork
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/netbirdio/netbird/shared/management/client/rest"
|
|
"github.com/netbirdio/netbird/shared/management/http/api"
|
|
)
|
|
|
|
func ptr[T any](v T) *T { return &v }
|
|
|
|
// newProvider creates an OpenAI-catalog provider with a dummy key (these tests
|
|
// never call the upstream) and registers cleanup.
|
|
func newProvider(t *testing.T, ctx context.Context, name string) api.AgentNetworkProvider {
|
|
t.Helper()
|
|
prov, err := srv.CreateProvider(ctx, api.AgentNetworkProviderRequest{
|
|
Name: name,
|
|
ProviderId: "openai_api",
|
|
UpstreamUrl: "https://api.openai.com",
|
|
ApiKey: ptr("sk-dummy-e2e-key"),
|
|
BootstrapCluster: ptr("eu.proxy.netbird.test"),
|
|
})
|
|
require.NoError(t, err, "create provider %q", name)
|
|
t.Cleanup(func() { _ = srv.DeleteProvider(context.Background(), prov.Id) })
|
|
return prov
|
|
}
|
|
|
|
// requireClientError asserts err is a REST APIError with a 4xx status.
|
|
func requireClientError(t *testing.T, err error) {
|
|
t.Helper()
|
|
var apiErr *rest.APIError
|
|
require.ErrorAs(t, err, &apiErr, "expected a REST APIError")
|
|
assert.GreaterOrEqual(t, apiErr.StatusCode, 400, "expected a 4xx status")
|
|
assert.Less(t, apiErr.StatusCode, 500, "expected a 4xx status")
|
|
}
|
|
|
|
// TestProviderLifecycle covers create → get → list → delete → 404.
|
|
func TestProviderLifecycle(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
prov := newProvider(t, ctx, "Provider Lifecycle")
|
|
assert.NotEmpty(t, prov.Id, "created provider must have an id")
|
|
assert.Equal(t, "openai_api", prov.ProviderId)
|
|
|
|
got, err := srv.GetProvider(ctx, prov.Id)
|
|
require.NoError(t, err, "get provider")
|
|
assert.Equal(t, prov.Id, got.Id)
|
|
|
|
list, err := srv.ListProviders(ctx)
|
|
require.NoError(t, err, "list providers")
|
|
var ids []string
|
|
for _, p := range list {
|
|
ids = append(ids, p.Id)
|
|
}
|
|
assert.Contains(t, ids, prov.Id, "created provider must appear in the list")
|
|
|
|
require.NoError(t, srv.DeleteProvider(ctx, prov.Id), "delete provider")
|
|
_, err = srv.GetProvider(ctx, prov.Id)
|
|
requireClientError(t, err)
|
|
}
|
|
|
|
// TestProviderValidation rejects a missing API key and an unknown catalog id.
|
|
func TestProviderValidation(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
_, err := srv.CreateProvider(ctx, api.AgentNetworkProviderRequest{
|
|
Name: "No Key",
|
|
ProviderId: "openai_api",
|
|
UpstreamUrl: "https://api.openai.com",
|
|
})
|
|
requireClientError(t, err)
|
|
|
|
_, err = srv.CreateProvider(ctx, api.AgentNetworkProviderRequest{
|
|
Name: "Unknown Catalog",
|
|
ProviderId: "totally_unknown_provider",
|
|
UpstreamUrl: "https://example.com",
|
|
ApiKey: ptr("sk-dummy"),
|
|
})
|
|
requireClientError(t, err)
|
|
}
|
|
|
|
// TestSettingsRoundTrip flips the collection toggles and confirms cluster /
|
|
// subdomain stay immutable, then restores the original state.
|
|
func TestSettingsRoundTrip(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Settings are bootstrapped on first provider create.
|
|
newProvider(t, ctx, "Settings Bootstrap")
|
|
|
|
before, err := srv.GetSettings(ctx)
|
|
require.NoError(t, err, "get settings")
|
|
require.NotEmpty(t, before.Cluster, "settings must carry an assigned cluster")
|
|
|
|
flipped, err := srv.UpdateSettings(ctx, api.AgentNetworkSettingsRequest{
|
|
EnableLogCollection: !before.EnableLogCollection,
|
|
EnablePromptCollection: !before.EnablePromptCollection,
|
|
RedactPii: !before.RedactPii,
|
|
})
|
|
require.NoError(t, err, "update settings")
|
|
assert.Equal(t, !before.EnableLogCollection, flipped.EnableLogCollection, "log collection toggle must flip")
|
|
assert.Equal(t, !before.EnablePromptCollection, flipped.EnablePromptCollection, "prompt collection toggle must flip")
|
|
assert.Equal(t, before.Cluster, flipped.Cluster, "cluster must be immutable across updates")
|
|
assert.Equal(t, before.Subdomain, flipped.Subdomain, "subdomain must be immutable across updates")
|
|
|
|
// Restore the original toggles.
|
|
_, err = srv.UpdateSettings(ctx, api.AgentNetworkSettingsRequest{
|
|
EnableLogCollection: before.EnableLogCollection,
|
|
EnablePromptCollection: before.EnablePromptCollection,
|
|
RedactPii: before.RedactPii,
|
|
})
|
|
require.NoError(t, err, "restore settings")
|
|
}
|
|
|
|
// TestPolicyWindowFloor rejects an enabled limit below the 60s window floor and
|
|
// accepts one at the floor.
|
|
func TestPolicyWindowFloor(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
grp, err := srv.API().Groups.Create(ctx, api.PostApiGroupsJSONRequestBody{Name: "e2e-policy-grp"})
|
|
require.NoError(t, err, "create source group")
|
|
t.Cleanup(func() { _ = srv.API().Groups.Delete(context.Background(), grp.Id) })
|
|
|
|
prov := newProvider(t, ctx, "Policy Provider")
|
|
|
|
limits := func(window int64) *api.AgentNetworkPolicyLimits {
|
|
return &api.AgentNetworkPolicyLimits{
|
|
TokenLimit: api.AgentNetworkPolicyTokenLimit{
|
|
Enabled: true,
|
|
GroupCap: 1000,
|
|
UserCap: 1000,
|
|
WindowSeconds: window,
|
|
},
|
|
}
|
|
}
|
|
|
|
_, err = srv.CreatePolicy(ctx, api.AgentNetworkPolicyRequest{
|
|
Name: "e2e-below-floor",
|
|
SourceGroups: []string{grp.Id},
|
|
DestinationProviderIds: []string{prov.Id},
|
|
Limits: limits(30),
|
|
})
|
|
requireClientError(t, err)
|
|
|
|
pol, err := srv.CreatePolicy(ctx, api.AgentNetworkPolicyRequest{
|
|
Name: "e2e-at-floor",
|
|
SourceGroups: []string{grp.Id},
|
|
DestinationProviderIds: []string{prov.Id},
|
|
Limits: limits(60),
|
|
})
|
|
require.NoError(t, err, "policy at the 60s floor must be accepted")
|
|
assert.NotEmpty(t, pol.Id, "created policy must have an id")
|
|
t.Cleanup(func() { _ = srv.DeletePolicy(context.Background(), pol.Id) })
|
|
}
|
|
|
|
// TestConsumptionList confirms the read endpoint always returns an array, never
|
|
// a 404/500.
|
|
func TestConsumptionList(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
rows, err := srv.ListConsumption(ctx)
|
|
require.NoError(t, err, "consumption list must not error")
|
|
assert.NotNil(t, rows, "consumption must be a JSON array (possibly empty)")
|
|
}
|