package pluginsimport ("encoding/json". "github.com/onsi/ginkgo/v2". "github.com/onsi/gomega")var _ = Describe("Manifest", func() {Describe("UnmarshalJSON", func() {It("parses a valid manifest", func() {data := []byte(`{"name": "Test Plugin","author": "Test Author","version": "1.0.0","description": "A test plugin","website": "https://example.com","permissions": {"http": {"reason": "Fetch metadata","requiredHosts": ["api.example.com", "*.musicbrainz.org"]}}}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).ToNot(HaveOccurred())Expect(m.Name).To(Equal("Test Plugin"))Expect(m.Author).To(Equal("Test Author"))Expect(m.Version).To(Equal("1.0.0"))Expect(*m.Description).To(Equal("A test plugin"))Expect(*m.Website).To(Equal("https://example.com"))Expect(m.Permissions.Http).ToNot(BeNil())Expect(*m.Permissions.Http.Reason).To(Equal("Fetch metadata"))Expect(m.Permissions.Http.RequiredHosts).To(ContainElements("api.example.com", "*.musicbrainz.org"))})It("parses a minimal manifest", func() {data := []byte(`{"name": "Minimal Plugin","author": "Author","version": "1.0.0"}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).ToNot(HaveOccurred())Expect(m.Name).To(Equal("Minimal Plugin"))Expect(m.Author).To(Equal("Author"))Expect(m.Version).To(Equal("1.0.0"))Expect(m.Description).To(BeNil())Expect(m.Permissions).To(BeNil())})It("returns an error for invalid JSON", func() {data := []byte(`{invalid json}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())})It("returns an error when name is missing", func() {data := []byte(`{"author": "Test Author", "version": "1.0.0"}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("name"))})It("returns an error when author is missing", func() {data := []byte(`{"name": "Test Plugin", "version": "1.0.0"}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("author"))})It("returns an error when version is missing", func() {data := []byte(`{"name": "Test Plugin", "author": "Test Author"}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("version"))})It("returns an error when name is empty", func() {data := []byte(`{"name": "", "author": "Test Author", "version": "1.0.0"}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("name"))})It("returns an error when author is empty", func() {data := []byte(`{"name": "Test Plugin", "author": "", "version": "1.0.0"}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("author"))})It("returns an error when version is empty", func() {data := []byte(`{"name": "Test Plugin", "author": "Test Author", "version": ""}`)var m Manifesterr := json.Unmarshal(data, &m)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("version"))})})Describe("ParseManifest", func() {It("parses a valid manifest with users permission", func() {data := []byte(`{"name": "Test Plugin","author": "Test Author","version": "1.0.0","permissions": {"subsonicapi": {},"users": {}}}`)m, err := ParseManifest(data)Expect(err).ToNot(HaveOccurred())Expect(m.Name).To(Equal("Test Plugin"))Expect(m.Permissions.Subsonicapi).ToNot(BeNil())Expect(m.Permissions.Users).ToNot(BeNil())})It("returns error for invalid JSON", func() {data := []byte(`{invalid}`)_, err := ParseManifest(data)Expect(err).To(HaveOccurred())})It("returns error when subsonicapi is requested without users permission", func() {data := []byte(`{"name": "Test Plugin","author": "Test Author","version": "1.0.0","permissions": {"subsonicapi": {}}}`)_, err := ParseManifest(data)Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("subsonicapi"))Expect(err.Error()).To(ContainSubstring("users"))})})Describe("Validate", func() {It("validates manifest with subsonicapi and users permissions", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Subsonicapi: &SubsonicAPIPermission{},Users: &UsersPermission{},},}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("returns error when subsonicapi without users permission", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Subsonicapi: &SubsonicAPIPermission{},},}err := m.Validate()Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("subsonicapi"))})It("validates manifest with matcher and library permissions", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Matcher: &MatcherPermission{},Library: &LibraryPermission{},},}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("returns error when matcher without library permission", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Matcher: &MatcherPermission{},},}err := m.Validate()Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("matcher"))Expect(err.Error()).To(ContainSubstring("library"))})It("validates manifest with scrobbleRetriever and users permissions", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{ScrobbleRetriever: &ScrobbleRetrieverPermission{},Users: &UsersPermission{},},}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("returns error when scrobbleRetriever without users permission", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{ScrobbleRetriever: &ScrobbleRetrieverPermission{},},}err := m.Validate()Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("scrobbleRetriever"))})It("validates manifest without subsonicapi", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Http: &HTTPPermission{},},}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("validates manifest without any permissions", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("validates manifest with valid config schema", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Config: &ConfigDefinition{Schema: map[string]any{"type": "object","properties": map[string]any{"api_key": map[string]any{"type": "string",},},},},}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("validates manifest with complex config schema", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Config: &ConfigDefinition{Schema: map[string]any{"type": "object","properties": map[string]any{"users": map[string]any{"type": "array","items": map[string]any{"type": "object","properties": map[string]any{"username": map[string]any{"type": "string"},"token": map[string]any{"type": "string"},},"required": []any{"username", "token"},},},},},},}err := m.Validate()Expect(err).ToNot(HaveOccurred())})It("returns error for invalid config schema - bad type", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Config: &ConfigDefinition{Schema: map[string]any{"type": "invalid_type",},},}err := m.Validate()Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("config schema"))})It("returns error for invalid config schema - bad minLength", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Config: &ConfigDefinition{Schema: map[string]any{"type": "object","properties": map[string]any{"name": map[string]any{"type": "string","minLength": "not_a_number",},},},},}err := m.Validate()Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("config schema"))})It("validates manifest without config", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",}err := m.Validate()Expect(err).ToNot(HaveOccurred())})})Describe("ValidateWithCapabilities", func() {It("validates scrobbler capability with users permission", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Users: &UsersPermission{},},}err := ValidateWithCapabilities(m, []Capability{CapabilityScrobbler})Expect(err).ToNot(HaveOccurred())})It("returns error when scrobbler capability without users permission", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",}err := ValidateWithCapabilities(m, []Capability{CapabilityScrobbler})Expect(err).To(HaveOccurred())Expect(err.Error()).To(ContainSubstring("scrobbler"))Expect(err.Error()).To(ContainSubstring("users"))})It("validates non-scrobbler capability without users permission", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",}err := ValidateWithCapabilities(m, []Capability{CapabilityMetadataAgent})Expect(err).ToNot(HaveOccurred())})It("validates multiple capabilities including scrobbler", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",Permissions: &Permissions{Users: &UsersPermission{},},}err := ValidateWithCapabilities(m, []Capability{CapabilityMetadataAgent, CapabilityScrobbler})Expect(err).ToNot(HaveOccurred())})It("validates with nil capabilities", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",}err := ValidateWithCapabilities(m, nil)Expect(err).ToNot(HaveOccurred())})It("validates with empty capabilities", func() {m := &Manifest{Name: "Test",Author: "Author",Version: "1.0.0",}err := ValidateWithCapabilities(m, []Capability{})Expect(err).ToNot(HaveOccurred())})})})var _ = Describe("Permissions.DeclaredNames", func() {It("returns nil for a nil receiver", func() {var p *PermissionsExpect(p.DeclaredNames()).To(BeEmpty())})It("returns declared names sorted", func() {p := &Permissions{Subsonicapi: &SubsonicAPIPermission{},Users: &UsersPermission{},}Expect(p.DeclaredNames()).To(Equal([]string{"subsonicapi", "users"}))})It("returns all declared names sorted regardless of field order", func() {p := &Permissions{Http: &HTTPPermission{},Artwork: &ArtworkPermission{},Cache: &CachePermission{},}Expect(p.DeclaredNames()).To(Equal([]string{"artwork", "cache", "http"}))})})