api/modernization_test.go

345 lines
9.6 KiB
Go
Raw Normal View History

// SPDX-License-Identifier: EUPL-1.2
package api_test
import (
"slices"
"testing"
"time"
api "dappco.re/go/core/api"
)
func TestEngine_GroupsIter(t *testing.T) {
e, _ := api.New()
g1 := &healthGroup{}
e.Register(g1)
var groups []api.RouteGroup
for g := range e.GroupsIter() {
groups = append(groups, g)
}
if len(groups) != 1 {
t.Fatalf("expected 1 group, got %d", len(groups))
}
if groups[0].Name() != "health-extra" {
t.Errorf("expected group name 'health-extra', got %q", groups[0].Name())
}
}
func TestEngine_GroupsIter_Good_SnapshotsCurrentGroups(t *testing.T) {
e, _ := api.New()
g1 := &healthGroup{}
g2 := &stubGroup{}
e.Register(g1)
iter := e.GroupsIter()
e.Register(g2)
var groups []api.RouteGroup
for g := range iter {
groups = append(groups, g)
}
if len(groups) != 1 {
t.Fatalf("expected iterator snapshot to contain 1 group, got %d", len(groups))
}
if groups[0].Name() != "health-extra" {
t.Fatalf("expected snapshot to preserve original group, got %q", groups[0].Name())
}
}
type streamGroupStub struct {
healthGroup
channels []string
}
func (s *streamGroupStub) Channels() []string { return s.channels }
func TestEngine_ChannelsIter(t *testing.T) {
e, _ := api.New()
g1 := &streamGroupStub{channels: []string{"ch1", "ch2"}}
g2 := &streamGroupStub{channels: []string{"ch3"}}
e.Register(g1)
e.Register(g2)
var channels []string
for ch := range e.ChannelsIter() {
channels = append(channels, ch)
}
expected := []string{"ch1", "ch2", "ch3"}
if !slices.Equal(channels, expected) {
t.Fatalf("expected channels %v, got %v", expected, channels)
}
}
func TestEngine_ChannelsIter_Good_SnapshotsCurrentChannels(t *testing.T) {
e, _ := api.New()
g1 := &streamGroupStub{channels: []string{"ch1", "ch2"}}
g2 := &streamGroupStub{channels: []string{"ch3"}}
e.Register(g1)
iter := e.ChannelsIter()
e.Register(g2)
var channels []string
for ch := range iter {
channels = append(channels, ch)
}
expected := []string{"ch1", "ch2"}
if !slices.Equal(channels, expected) {
t.Fatalf("expected snapshot channels %v, got %v", expected, channels)
}
}
func TestEngine_CacheConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
e, _ := api.New(api.WithCacheLimits(5*time.Minute, 10, 1024))
cfg := e.CacheConfig()
if !cfg.Enabled {
t.Fatal("expected cache config to be enabled")
}
if cfg.TTL != 5*time.Minute {
t.Fatalf("expected TTL %v, got %v", 5*time.Minute, cfg.TTL)
}
if cfg.MaxEntries != 10 {
t.Fatalf("expected MaxEntries 10, got %d", cfg.MaxEntries)
}
if cfg.MaxBytes != 1024 {
t.Fatalf("expected MaxBytes 1024, got %d", cfg.MaxBytes)
}
}
func TestEngine_RuntimeConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
broker := api.NewSSEBroker()
e, err := api.New(
api.WithSwagger("Runtime API", "Runtime snapshot", "1.2.3"),
api.WithSwaggerPath("/docs"),
api.WithCacheLimits(5*time.Minute, 10, 1024),
api.WithGraphQL(newTestSchema(), api.WithPlayground()),
api.WithI18n(api.I18nConfig{
DefaultLocale: "en-GB",
Supported: []string{"en-GB", "fr"},
}),
api.WithWSPath("/socket"),
api.WithSSE(broker),
api.WithSSEPath("/events"),
api.WithAuthentik(api.AuthentikConfig{
Issuer: "https://auth.example.com",
ClientID: "runtime-client",
TrustedProxy: true,
PublicPaths: []string{"/public", "/docs"},
}),
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
cfg := e.RuntimeConfig()
if !cfg.Swagger.Enabled {
t.Fatal("expected swagger snapshot to be enabled")
}
if cfg.Swagger.Path != "/docs" {
t.Fatalf("expected swagger path /docs, got %q", cfg.Swagger.Path)
}
if cfg.Transport.SwaggerPath != "/docs" {
t.Fatalf("expected transport swagger path /docs, got %q", cfg.Transport.SwaggerPath)
}
if cfg.Transport.GraphQLPlaygroundPath != "/graphql/playground" {
t.Fatalf("expected transport graphql playground path /graphql/playground, got %q", cfg.Transport.GraphQLPlaygroundPath)
}
if !cfg.Cache.Enabled || cfg.Cache.TTL != 5*time.Minute {
t.Fatalf("expected cache snapshot to be populated, got %+v", cfg.Cache)
}
if !cfg.GraphQL.Enabled {
t.Fatal("expected GraphQL snapshot to be enabled")
}
if cfg.GraphQL.Path != "/graphql" {
t.Fatalf("expected GraphQL path /graphql, got %q", cfg.GraphQL.Path)
}
if !cfg.GraphQL.Playground {
t.Fatal("expected GraphQL playground snapshot to be enabled")
}
if cfg.GraphQL.PlaygroundPath != "/graphql/playground" {
t.Fatalf("expected GraphQL playground path /graphql/playground, got %q", cfg.GraphQL.PlaygroundPath)
}
if cfg.I18n.DefaultLocale != "en-GB" {
t.Fatalf("expected default locale en-GB, got %q", cfg.I18n.DefaultLocale)
}
if !slices.Equal(cfg.I18n.Supported, []string{"en-GB", "fr"}) {
t.Fatalf("expected supported locales [en-GB fr], got %v", cfg.I18n.Supported)
}
if cfg.Authentik.Issuer != "https://auth.example.com" {
t.Fatalf("expected Authentik issuer https://auth.example.com, got %q", cfg.Authentik.Issuer)
}
if cfg.Authentik.ClientID != "runtime-client" {
t.Fatalf("expected Authentik client ID runtime-client, got %q", cfg.Authentik.ClientID)
}
if !cfg.Authentik.TrustedProxy {
t.Fatal("expected Authentik trusted proxy to be enabled")
}
if !slices.Equal(cfg.Authentik.PublicPaths, []string{"/public", "/docs"}) {
t.Fatalf("expected Authentik public paths [/public /docs], got %v", cfg.Authentik.PublicPaths)
}
}
func TestEngine_RuntimeConfig_Good_EmptyOnNilEngine(t *testing.T) {
var e *api.Engine
cfg := e.RuntimeConfig()
if cfg.Swagger.Enabled || cfg.Transport.SwaggerEnabled || cfg.GraphQL.Enabled || cfg.Cache.Enabled || cfg.I18n.DefaultLocale != "" || cfg.Authentik.Issuer != "" {
t.Fatalf("expected zero-value runtime config, got %+v", cfg)
}
}
func TestEngine_AuthentikConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
e, _ := api.New(api.WithAuthentik(api.AuthentikConfig{
Issuer: "https://auth.example.com",
ClientID: "client",
TrustedProxy: true,
PublicPaths: []string{"/public", "/docs"},
}))
cfg := e.AuthentikConfig()
if cfg.Issuer != "https://auth.example.com" {
t.Fatalf("expected issuer https://auth.example.com, got %q", cfg.Issuer)
}
if cfg.ClientID != "client" {
t.Fatalf("expected client ID client, got %q", cfg.ClientID)
}
if !cfg.TrustedProxy {
t.Fatal("expected trusted proxy to be enabled")
}
if !slices.Equal(cfg.PublicPaths, []string{"/public", "/docs"}) {
t.Fatalf("expected public paths [/public /docs], got %v", cfg.PublicPaths)
}
}
func TestEngine_AuthentikConfig_Good_ClonesPublicPaths(t *testing.T) {
publicPaths := []string{"/public", "/docs"}
e, _ := api.New(api.WithAuthentik(api.AuthentikConfig{
Issuer: "https://auth.example.com",
PublicPaths: publicPaths,
}))
cfg := e.AuthentikConfig()
publicPaths[0] = "/mutated"
if cfg.PublicPaths[0] != "/public" {
t.Fatalf("expected snapshot to preserve original public paths, got %v", cfg.PublicPaths)
}
}
func TestEngine_AuthentikConfig_Good_NormalisesPublicPaths(t *testing.T) {
e, _ := api.New(api.WithAuthentik(api.AuthentikConfig{
PublicPaths: []string{" /public/ ", "docs", "/public"},
}))
cfg := e.AuthentikConfig()
expected := []string{"/public", "/docs"}
if !slices.Equal(cfg.PublicPaths, expected) {
t.Fatalf("expected normalised public paths %v, got %v", expected, cfg.PublicPaths)
}
}
func TestEngine_AuthentikConfig_Good_BlankPublicPathsRemainNil(t *testing.T) {
e, _ := api.New(api.WithAuthentik(api.AuthentikConfig{
PublicPaths: []string{" ", "\t", ""},
}))
cfg := e.AuthentikConfig()
if cfg.PublicPaths != nil {
t.Fatalf("expected blank public paths to collapse to nil, got %v", cfg.PublicPaths)
}
}
func TestEngine_Register_Good_IgnoresNilGroups(t *testing.T) {
e, _ := api.New()
var nilGroup *healthGroup
e.Register(nilGroup)
g1 := &healthGroup{}
e.Register(g1)
groups := e.Groups()
if len(groups) != 1 {
t.Fatalf("expected 1 registered group, got %d", len(groups))
}
if groups[0].Name() != "health-extra" {
t.Fatalf("expected the original group to be preserved, got %q", groups[0].Name())
}
}
func TestToolBridge_Iterators(t *testing.T) {
b := api.NewToolBridge("/tools")
desc := api.ToolDescriptor{Name: "test", Group: "g1"}
b.Add(desc, nil)
// Test ToolsIter
var tools []api.ToolDescriptor
for t := range b.ToolsIter() {
tools = append(tools, t)
}
if len(tools) != 1 || tools[0].Name != "test" {
t.Errorf("ToolsIter failed, got %v", tools)
}
// Test DescribeIter
var descs []api.RouteDescription
for d := range b.DescribeIter() {
descs = append(descs, d)
}
if len(descs) != 1 || descs[0].Path != "/test" {
t.Errorf("DescribeIter failed, got %v", descs)
}
}
func TestToolBridge_Iterators_Good_SnapshotCurrentTools(t *testing.T) {
b := api.NewToolBridge("/tools")
b.Add(api.ToolDescriptor{Name: "first", Group: "g1"}, nil)
toolsIter := b.ToolsIter()
descsIter := b.DescribeIter()
b.Add(api.ToolDescriptor{Name: "second", Group: "g2"}, nil)
var tools []api.ToolDescriptor
for tool := range toolsIter {
tools = append(tools, tool)
}
var descs []api.RouteDescription
for desc := range descsIter {
descs = append(descs, desc)
}
if len(tools) != 1 || tools[0].Name != "first" {
t.Fatalf("expected ToolsIter snapshot to contain the original tool, got %v", tools)
}
if len(descs) != 1 || descs[0].Path != "/first" {
t.Fatalf("expected DescribeIter snapshot to contain the original tool, got %v", descs)
}
}
func TestCodegen_SupportedLanguagesIter(t *testing.T) {
var langs []string
for l := range api.SupportedLanguagesIter() {
langs = append(langs, l)
}
if !slices.Contains(langs, "go") {
t.Errorf("SupportedLanguagesIter missing 'go'")
}
// Should be sorted
if !slices.IsSorted(langs) {
t.Errorf("SupportedLanguagesIter should be sorted, got %v", langs)
}
}