Files
upstream-ory-keto/internal/driver/config/namespace_watcher_test.go
upstream-mirror 9456a4e922
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:08:34 +05:00

251 lines
6.0 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package config
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/ghodss/yaml"
"github.com/ory/x/logrusx"
"github.com/ory/x/watcherx"
"github.com/pelletier/go-toml"
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ory/keto/internal/namespace"
)
func TestNamespaceProvider(t *testing.T) {
setup := func(t *testing.T, target string) (*NamespaceWatcher, *test.Hook) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
hook := &test.Hook{}
l := logrusx.New("", "", logrusx.WithHook(hook))
nw, err := NewNamespaceWatcher(ctx, l, target)
require.NoError(t, err)
return nw, hook
}
writeNamespace := func(t *testing.T, n *namespace.Namespace, fn string) {
var marshal func(interface{}) ([]byte, error)
switch ext := filepath.Ext(fn); ext {
case ".yaml", ".yml":
marshal = yaml.Marshal
case ".toml":
marshal = toml.Marshal
case ".json":
marshal = json.Marshal
default:
t.Logf("got unexpected file extension %s", ext)
t.FailNow()
}
nEnc, err := marshal(n)
require.NoError(t, err)
require.NoError(t, os.WriteFile(fn, nEnc, 0600))
}
writeDir := func(t *testing.T, dir string, files map[string]interface{}) {
for fn, fc := range files {
fp := filepath.Join(dir, fn)
require.NoError(t, os.MkdirAll(filepath.Dir(fp), 0700))
if n, ok := fc.(*namespace.Namespace); ok {
writeNamespace(t, n, fp)
continue
}
s, ok := fc.(string)
if !ok {
t.Logf("expected file content to be string or *namespace.Namespace but got %T", fc)
t.FailNow()
}
require.NoError(t, os.WriteFile(fp, []byte(s), 0600))
}
}
writeJsonNamespace := func(t *testing.T) (string, *namespace.Namespace) {
dir := t.TempDir()
n := &namespace.Namespace{
Name: "test namespace 1",
}
fn := filepath.Join(dir, "n.json")
writeNamespace(t, n, fn)
return fn, n
}
t.Run("case=loads JSON namespace file", func(t *testing.T) {
fn, n := writeJsonNamespace(t)
ws, _ := setup(t, "file://"+fn)
nspaces, err := ws.Namespaces(context.Background())
require.NoError(t, err)
assert.Equal(t, []*namespace.Namespace{n}, nspaces)
})
t.Run("case=reads namespace files from directory", func(t *testing.T) {
dir := t.TempDir()
files := map[string]interface{}{
"b.yml": &namespace.Namespace{
Name: "b",
},
"a.toml": &namespace.Namespace{
Name: "a",
},
"c.json": &namespace.Namespace{
Name: "c",
},
}
writeDir(t, dir, files)
nw, _ := setup(t, "file://"+dir)
nspaces, err := nw.Namespaces(context.Background())
require.NoError(t, err)
for _, n := range files {
assert.Contains(t, nspaces, n.(*namespace.Namespace))
}
nsfs := nw.NamespaceFiles()
assert.Equal(t, len(nspaces), len(nsfs))
for _, n := range nsfs {
assert.NotNil(t, n.Contents)
assert.NotNil(t, n.Parser)
}
})
t.Run("case=ignores but warns about unsupported file extensions", func(t *testing.T) {
dir := t.TempDir()
n := &namespace.Namespace{
Name: "some name",
}
nJson, err := json.Marshal(n)
require.NoError(t, err)
writeDir(t, dir, map[string]interface{}{
"unsupported.file": "foo bar\n",
"supported.json": string(nJson),
})
nw, hook := setup(t, "file://"+dir)
require.Len(t, hook.Entries, 1)
assert.Equal(t, logrus.WarnLevel, hook.Entries[0].Level, "%+v", hook.Entries[0])
assert.True(t, strings.HasSuffix(hook.Entries[0].Data["file_name"].(string), "unsupported.file"))
namespaces, err := nw.Namespaces(context.Background())
require.NoError(t, err)
require.Len(t, namespaces, 1)
assert.Equal(t, n, namespaces[0])
})
t.Run("case=still returns successful namespace if one errors", func(t *testing.T) {
dir := t.TempDir()
n := &namespace.Namespace{
Name: "some name",
}
nJson, err := json.Marshal(n)
require.NoError(t, err)
writeDir(t, dir, map[string]interface{}{
"malformed.yml": "foo bar\n",
"correct.json": string(nJson),
})
nw, hook := setup(t, "file://"+dir)
require.Len(t, hook.Entries, 1)
assert.Equal(t, logrus.ErrorLevel, hook.Entries[0].Level, "%+v", hook.Entries[0])
assert.True(t, strings.HasSuffix(hook.Entries[0].Data["file_name"].(string), "malformed.yml"))
namespaces, err := nw.Namespaces(context.Background())
require.NoError(t, err)
require.Len(t, namespaces, 1)
assert.Equal(t, n, namespaces[0])
// files are included even if ns is unparsable
nsfs := nw.NamespaceFiles()
assert.Equal(t, 2, len(nsfs))
})
t.Run("method=should reload", func(t *testing.T) {
nw := &NamespaceWatcher{
target: "foo",
}
assert.False(t, nw.ShouldReload("foo"))
assert.True(t, nw.ShouldReload("bar"))
assert.True(t, nw.ShouldReload([]*namespace.Namespace{}))
})
}
type noopEventHandler struct{}
func (noopEventHandler) handleRemove(*watcherx.RemoveEvent) {}
func (noopEventHandler) handleChange(*watcherx.ChangeEvent) {}
func (noopEventHandler) handleError(*watcherx.ErrorEvent) {}
func TestStartEventHandlerDoesNotCloseInitialDoneTwice(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
t.Cleanup(cancel)
eventCh := make(watcherx.EventChannel)
done := make(chan int, 1)
initialEventsProcessed := make(chan struct{})
handlerReturned := make(chan any, 1)
go func() {
defer func() {
handlerReturned <- recover()
}()
startEventHandler(ctx, eventCh, noopEventHandler{}, done, initialEventsProcessed, logrusx.New("", ""))
}()
done <- 0
select {
case <-initialEventsProcessed:
case <-time.After(time.Second):
t.Fatal("timed out waiting for initial events to be processed")
}
close(done)
select {
case recovered := <-handlerReturned:
require.Nil(t, recovered)
t.Fatal("event handler returned before context cancellation")
case <-time.After(100 * time.Millisecond):
}
cancel()
select {
case recovered := <-handlerReturned:
require.Nil(t, recovered)
case <-time.After(time.Second):
t.Fatal("timed out waiting for event handler to stop")
}
}