package plugin import ( "strings" "testing" ) func TestDokiValidation(t *testing.T) { tests := []struct { name string ref PluginRef wantError string }{ { name: "Missing Fetcher", ref: PluginRef{ Name: "Invalid Doki", Type: "doki", }, wantError: "doki plugin fetcher must be 'file' or 'internal'", }, { name: "Invalid Fetcher", ref: PluginRef{ Name: "Invalid Fetcher", Type: "doki", Fetcher: "http", }, wantError: "doki plugin fetcher must be 'file' or 'internal'", }, { name: "File Fetcher Missing URL", ref: PluginRef{ Name: "File No URL", Type: "doki", Fetcher: "file", }, wantError: "doki plugin with file fetcher requires 'url'", }, { name: "Internal Fetcher Missing Text", ref: PluginRef{ Name: "Internal No Text", Type: "doki", Fetcher: "internal", }, wantError: "doki plugin with internal fetcher requires 'text'", }, { name: "Doki with Tiki fields", ref: PluginRef{ Name: "Doki with Filter", Type: "doki", Fetcher: "internal", Text: "ok", Filter: "status='ready'", }, wantError: "doki plugin cannot have 'filter'", }, { name: "Valid File Fetcher", ref: PluginRef{ Name: "Valid File", Type: "doki", Fetcher: "file", URL: "http://example.com", }, wantError: "", }, { name: "Valid Internal Fetcher", ref: PluginRef{ Name: "Valid Internal", Type: "doki", Fetcher: "internal", Text: "content", }, wantError: "", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := loadPluginFromRef(tc.ref, "") if tc.wantError != "" { if err == nil { t.Errorf("Expected error containing '%s', got nil", tc.wantError) } else if !strings.Contains(err.Error(), tc.wantError) { t.Errorf("Expected error containing '%s', got '%v'", tc.wantError, err) } } else { if err != nil { t.Errorf("Expected no error, got '%v'", err) } } }) } } func TestTikiValidation(t *testing.T) { tests := []struct { name string ref PluginRef wantError string }{ { name: "Tiki with Doki fields (Fetcher)", ref: PluginRef{ Name: "Tiki with Fetcher", Type: "tiki", Filter: "status='ready'", Fetcher: "file", }, wantError: "tiki plugin cannot have 'fetcher'", }, { name: "Tiki with Doki fields (Text)", ref: PluginRef{ Name: "Tiki with Text", Type: "tiki", Filter: "status='ready'", Text: "text", }, wantError: "tiki plugin cannot have 'text'", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, err := loadPluginFromRef(tc.ref, "") if tc.wantError != "" { if err == nil { t.Errorf("Expected error containing '%s', got nil", tc.wantError) } else if !strings.Contains(err.Error(), tc.wantError) { t.Errorf("Expected error containing '%s', got '%v'", tc.wantError, err) } } else { if err != nil { t.Errorf("Expected no error, got '%v'", err) } } }) } } func TestParsePluginConfig_InvalidKey(t *testing.T) { cfg := pluginFileConfig{ Name: "Test", Key: "InvalidKey", Type: "tiki", } _, err := parsePluginConfig(cfg, "test.yaml") if err == nil { t.Fatal("Expected error for invalid key format") } if !strings.Contains(err.Error(), "parsing key") { t.Errorf("Expected 'parsing key' error, got: %v", err) } } func TestParsePluginConfig_DefaultTikiType(t *testing.T) { cfg := pluginFileConfig{ Name: "Test", Key: "T", Panes: []PluginPaneConfig{ {Name: "Todo", Filter: "status='ready'"}, }, // Type not specified, should default to "tiki" } plugin, err := parsePluginConfig(cfg, "test.yaml") if err != nil { t.Fatalf("Expected no error, got: %v", err) } if _, ok := plugin.(*TikiPlugin); !ok { t.Errorf("Expected TikiPlugin when type not specified, got %T", plugin) } } func TestParsePluginConfig_UnknownType(t *testing.T) { cfg := pluginFileConfig{ Name: "Test", Key: "T", Type: "unknown", } _, err := parsePluginConfig(cfg, "test.yaml") if err == nil { t.Fatal("Expected error for unknown plugin type") } expected := "unknown plugin type: unknown" if err.Error() != expected { t.Errorf("Expected error '%s', got: %v", expected, err) } } func TestParsePluginConfig_TikiWithInvalidFilter(t *testing.T) { cfg := pluginFileConfig{ Name: "Test", Key: "T", Type: "tiki", Filter: "invalid ( filter", } _, err := parsePluginConfig(cfg, "test.yaml") if err == nil { t.Fatal("Expected error for invalid top-level filter") } if !strings.Contains(err.Error(), "tiki plugin cannot have 'filter'") { t.Errorf("Expected 'cannot have filter' error, got: %v", err) } } // TestParsePluginConfig_TikiWithInvalidSort removed - the sort parser is very lenient // and accepts most field names. Invalid syntax would be caught by ParseSort internally. func TestParsePluginConfig_DokiWithSort(t *testing.T) { cfg := pluginFileConfig{ Name: "Test", Key: "T", Type: "doki", Fetcher: "internal", Text: "content", Sort: "Priority", // Doki shouldn't have sort } _, err := parsePluginConfig(cfg, "test.yaml") if err == nil { t.Fatal("Expected error for doki with sort field") } if !strings.Contains(err.Error(), "doki plugin cannot have 'sort'") { t.Errorf("Expected 'cannot have sort' error, got: %v", err) } } func TestParsePluginConfig_DokiWithView(t *testing.T) { cfg := pluginFileConfig{ Name: "Test", Key: "T", Type: "doki", Fetcher: "internal", Text: "content", View: "expanded", // Doki shouldn't have view } _, err := parsePluginConfig(cfg, "test.yaml") if err == nil { t.Fatal("Expected error for doki with view field") } if !strings.Contains(err.Error(), "doki plugin cannot have 'view'") { t.Errorf("Expected 'cannot have view' error, got: %v", err) } } func TestParsePluginYAML_InvalidYAML(t *testing.T) { invalidYAML := []byte("invalid: yaml: content:") _, err := parsePluginYAML(invalidYAML, "test.yaml") if err == nil { t.Fatal("Expected error for invalid YAML") } if !strings.Contains(err.Error(), "parsing yaml") { t.Errorf("Expected 'parsing yaml' error, got: %v", err) } } func TestParsePluginYAML_ValidTiki(t *testing.T) { validYAML := []byte(` name: Test Plugin key: T type: tiki panes: - name: Todo columns: 4 filter: status = 'ready' sort: Priority view: expanded foreground: "#ff0000" background: "#0000ff" `) plugin, err := parsePluginYAML(validYAML, "test.yaml") if err != nil { t.Fatalf("Expected no error, got: %v", err) } tikiPlugin, ok := plugin.(*TikiPlugin) if !ok { t.Fatalf("Expected TikiPlugin, got %T", plugin) } if tikiPlugin.GetName() != "Test Plugin" { t.Errorf("Expected name 'Test Plugin', got %q", tikiPlugin.GetName()) } if tikiPlugin.ViewMode != "expanded" { t.Errorf("Expected view mode 'expanded', got %q", tikiPlugin.ViewMode) } if len(tikiPlugin.Panes) != 1 { t.Fatalf("Expected 1 pane, got %d", len(tikiPlugin.Panes)) } if tikiPlugin.Panes[0].Columns != 4 { t.Errorf("Expected pane columns 4, got %d", tikiPlugin.Panes[0].Columns) } } func TestParsePluginYAML_ValidDoki(t *testing.T) { validYAML := []byte(` name: Doc Plugin key: D type: doki fetcher: file url: http://example.com/doc foreground: "#00ff00" `) plugin, err := parsePluginYAML(validYAML, "test.yaml") if err != nil { t.Fatalf("Expected no error, got: %v", err) } dokiPlugin, ok := plugin.(*DokiPlugin) if !ok { t.Fatalf("Expected DokiPlugin, got %T", plugin) } if dokiPlugin.GetName() != "Doc Plugin" { t.Errorf("Expected name 'Doc Plugin', got %q", dokiPlugin.GetName()) } if dokiPlugin.Fetcher != "file" { t.Errorf("Expected fetcher 'file', got %q", dokiPlugin.Fetcher) } if dokiPlugin.URL != "http://example.com/doc" { t.Errorf("Expected URL, got %q", dokiPlugin.URL) } }