From 7bbafcbf289ea82e8b84b4d007c793aff37c0933 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Sun, 11 Oct 2026 13:17:41 +0200 Subject: [PATCH] test(16.3-04): pin Journal token scope registration and lang keys Assert journal and journal:read scopes, methods, permissions, and phrase keys in en/pl lang.yaml. Co-authored-by: Cursor --- token_scopes_test.go | 117 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 token_scopes_test.go diff --git a/token_scopes_test.go b/token_scopes_test.go new file mode 100644 index 0000000..7935de4 --- /dev/null +++ b/token_scopes_test.go @@ -0,0 +1,117 @@ +package journal_test + +import ( + "os" + "path/filepath" + "testing" + + journal "git.golem15.com/golem15/sm-journal-plugin" + "git.golem15.com/golem15/summercms/modules/pact" + "github.com/goccy/go-yaml" +) + +func TestJournalTokenScopes(t *testing.T) { + p := &journal.Plugin{} + scopes := p.TokenScopes() + if len(scopes) != 2 { + t.Fatalf("TokenScopes len = %d, want 2", len(scopes)) + } + byName := map[string]pact.TokenScope{} + for _, s := range scopes { + byName[s.Name] = s + } + journalScope, ok := byName["journal"] + if !ok { + t.Fatal("missing journal scope") + } + readScope, ok := byName["journal:read"] + if !ok { + t.Fatal("missing journal:read scope") + } + if len(journalScope.Permissions) != 1 || journalScope.Permissions[0] != "golem15.journal.*" { + t.Fatalf("journal permissions = %#v", journalScope.Permissions) + } + if len(readScope.Permissions) != 1 || readScope.Permissions[0] != "golem15.journal.*" { + t.Fatalf("journal:read permissions = %#v", readScope.Permissions) + } + all := []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"} + if got := journalScope.Methods; !stringSlicesEqual(got, all) { + t.Fatalf("journal methods = %#v, want %#v", got, all) + } + if got := readScope.Methods; !stringSlicesEqual(got, []string{"GET", "HEAD"}) { + t.Fatalf("journal:read methods = %#v", got) + } + + keys := []string{ + journalScope.Label, journalScope.Description, + readScope.Label, readScope.Description, + } + for _, lang := range []string{"en", "pl"} { + path := filepath.Join("lang", lang, "lang.yaml") + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + var root map[string]any + if err := yaml.Unmarshal(raw, &root); err != nil { + t.Fatalf("yaml %s: %v", path, err) + } + for _, key := range keys { + if !phraseExists(root, key) { + t.Fatalf("%s missing phrase key %s", path, key) + } + } + } +} + +func stringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// phraseExists resolves plugin keys like golem15.journal::lang.scopes.journal +// against the plugin lang.yaml root (scopes.journal under the file). +func phraseExists(root map[string]any, key string) bool { + const prefix = "golem15.journal::lang." + if len(key) < len(prefix) || key[:len(prefix)] != prefix { + return false + } + parts := splitDot(key[len(prefix):]) + var cur any = root + for _, p := range parts { + m, ok := cur.(map[string]any) + if !ok { + return false + } + cur, ok = m[p] + if !ok { + return false + } + } + switch v := cur.(type) { + case string: + return v != "" + default: + return cur != nil + } +} + +func splitDot(s string) []string { + out := []string{} + start := 0 + for i := 0; i < len(s); i++ { + if s[i] == '.' { + out = append(out, s[start:i]) + start = i + 1 + } + } + out = append(out, s[start:]) + return out +}