@@ -426,86 +426,136 @@ func TestManifestEngineFieldsLoadFromFile(t *testing.T) {
426426 }
427427}
428428
429- func TestPluginManifest_LegacyCapabilities (t * testing.T ) {
430- // Legacy format: capabilities is a JSON object with configProvider, moduleTypes, etc.
431- legacyJSON := `{
432- "name": "legacy-plugin",
429+ // TestManifestLegacyCapabilitiesObject verifies that a plugin.json whose
430+ // "capabilities" field is a plain JSON object (the format used by external
431+ // plugins such as workflow-plugin-authz) is parsed without error and that the
432+ // type lists nested inside the object are promoted to the manifest's top-level
433+ // ModuleTypes/StepTypes/TriggerTypes fields.
434+ func TestManifestLegacyCapabilitiesObject (t * testing.T ) {
435+ const legacyJSON = `{
436+ "name": "workflow-plugin-authz",
433437 "version": "1.0.0",
434- "author": "Test",
435- "description": "Legacy capabilities test",
438+ "description": "RBAC authorization plugin using Casbin",
439+ "author": "GoCodeAlone",
440+ "license": "MIT",
441+ "type": "external",
442+ "tier": "core",
443+ "minEngineVersion": "0.3.11",
444+ "keywords": ["authz", "rbac", "casbin", "authorization", "policy"],
445+ "homepage": "https://github.com/GoCodeAlone/workflow-plugin-authz",
446+ "repository": "https://github.com/GoCodeAlone/workflow-plugin-authz",
436447 "capabilities": {
437- "configProvider": true,
438- "moduleTypes": ["test.module"],
439- "stepTypes": ["step.test"],
440- "triggerTypes": ["trigger.test"]
448+ "configProvider": false,
449+ "moduleTypes": ["authz.casbin"],
450+ "stepTypes": [
451+ "step.authz_check_casbin",
452+ "step.authz_add_policy",
453+ "step.authz_remove_policy",
454+ "step.authz_role_assign"
455+ ],
456+ "triggerTypes": []
441457 }
442458 }`
443459
444460 var m PluginManifest
445461 if err := json .Unmarshal ([]byte (legacyJSON ), & m ); err != nil {
446- t .Fatalf ("Unmarshal legacy capabilities: %v" , err )
462+ t .Fatalf ("unexpected unmarshal error for legacy capabilities object : %v" , err )
447463 }
448- if len (m .ModuleTypes ) != 1 || m .ModuleTypes [0 ] != "test.module" {
449- t .Errorf ("ModuleTypes = %v, want [test.module]" , m .ModuleTypes )
450- }
451- if len (m .StepTypes ) != 1 || m .StepTypes [0 ] != "step.test" {
452- t .Errorf ("StepTypes = %v, want [step.test]" , m .StepTypes )
453- }
454- if len (m .TriggerTypes ) != 1 || m .TriggerTypes [0 ] != "trigger.test" {
455- t .Errorf ("TriggerTypes = %v, want [trigger.test]" , m .TriggerTypes )
456- }
457- // Legacy object format should not populate Capabilities slice
464+
465+ // Capabilities array should be nil / empty – the object format has no CapabilityDecl items.
458466 if len (m .Capabilities ) != 0 {
459- t .Errorf ("Capabilities = %v, want empty for legacy object format " , m .Capabilities )
467+ t .Errorf ("Capabilities = %v, want empty" , m .Capabilities )
460468 }
461- }
462469
463- func TestPluginManifest_NewCapabilitiesArrayFormat (t * testing.T ) {
464- // New format: capabilities is a JSON array of CapabilityDecl
465- newJSON := `{
466- "name": "new-plugin",
467- "version": "1.0.0",
468- "author": "Test",
469- "description": "New capabilities test",
470- "moduleTypes": ["test.module"],
471- "stepTypes": ["step.test"],
472- "capabilities": [{"name": "step.test", "role": "provider"}]
473- }`
470+ // moduleTypes from the nested object should be promoted to the top level.
471+ if len (m .ModuleTypes ) != 1 || m .ModuleTypes [0 ] != "authz.casbin" {
472+ t .Errorf ("ModuleTypes = %v, want [authz.casbin]" , m .ModuleTypes )
473+ }
474474
475- var m PluginManifest
476- if err := json .Unmarshal ([]byte (newJSON ), & m ); err != nil {
477- t .Fatalf ("Unmarshal new capabilities: %v" , err )
475+ // stepTypes should be promoted.
476+ wantSteps := []string {
477+ "step.authz_check_casbin" ,
478+ "step.authz_add_policy" ,
479+ "step.authz_remove_policy" ,
480+ "step.authz_role_assign" ,
478481 }
479- if len (m .Capabilities ) != 1 || m .Capabilities [0 ].Name != "step.test" {
480- t .Errorf ("Capabilities = %v, want [{step.test provider 0}]" , m .Capabilities )
482+ if len (m .StepTypes ) != len (wantSteps ) {
483+ t .Errorf ("StepTypes len = %d, want %d; got %v" , len (m .StepTypes ), len (wantSteps ), m .StepTypes )
484+ } else {
485+ for i , want := range wantSteps {
486+ if m .StepTypes [i ] != want {
487+ t .Errorf ("StepTypes[%d] = %q, want %q" , i , m .StepTypes [i ], want )
488+ }
489+ }
481490 }
482- if len (m .ModuleTypes ) != 1 || m .ModuleTypes [0 ] != "test.module" {
483- t .Errorf ("ModuleTypes = %v, want [test.module]" , m .ModuleTypes )
491+
492+ // triggerTypes is an empty array – TriggerTypes should remain nil/empty.
493+ if len (m .TriggerTypes ) != 0 {
494+ t .Errorf ("TriggerTypes = %v, want empty" , m .TriggerTypes )
484495 }
485496}
486497
487- func TestPluginManifest_LegacyCapabilitiesMergesWithTopLevel (t * testing.T ) {
488- // Top-level fields should be merged with types from legacy capabilities object
489- legacyJSON := `{
490- "name": "merged-plugin",
498+ // TestManifestLegacyCapabilitiesObjectFile verifies that LoadManifest succeeds
499+ // for a plugin.json that uses the legacy object-style capabilities field.
500+ func TestManifestLegacyCapabilitiesObjectFile (t * testing.T ) {
501+ const legacyJSON = `{
502+ "name": "workflow-plugin-authz",
491503 "version": "1.0.0",
492- "author": "Test",
493- "description": "Merge test",
494- "moduleTypes": ["existing.module"],
504+ "description": "RBAC authorization plugin",
505+ "author": "GoCodeAlone",
495506 "capabilities": {
496- "moduleTypes": ["caps.module"],
497- "stepTypes": ["step.caps"]
507+ "moduleTypes": ["authz.casbin"],
508+ "stepTypes": ["step.authz_check"],
509+ "triggerTypes": []
498510 }
499511 }`
500512
501- var m PluginManifest
502- if err := json .Unmarshal ([]byte (legacyJSON ), & m ); err != nil {
503- t .Fatalf ("Unmarshal: %v" , err )
513+ dir := t .TempDir ()
514+ path := filepath .Join (dir , "plugin.json" )
515+ if err := os .WriteFile (path , []byte (legacyJSON ), 0644 ); err != nil {
516+ t .Fatal (err )
517+ }
518+
519+ m , err := LoadManifest (path )
520+ if err != nil {
521+ t .Fatalf ("LoadManifest error: %v" , err )
522+ }
523+ if len (m .ModuleTypes ) != 1 || m .ModuleTypes [0 ] != "authz.casbin" {
524+ t .Errorf ("ModuleTypes = %v, want [authz.casbin]" , m .ModuleTypes )
504525 }
505- if len (m .ModuleTypes ) != 2 {
506- t .Errorf ("ModuleTypes = %v, want [existing.module caps.module ]" , m .ModuleTypes )
526+ if len (m .StepTypes ) != 1 || m . StepTypes [ 0 ] != "step.authz_check" {
527+ t .Errorf ("StepTypes = %v, want [step.authz_check ]" , m .StepTypes )
507528 }
508- if len (m .StepTypes ) != 1 || m .StepTypes [0 ] != "step.caps" {
509- t .Errorf ("StepTypes = %v, want [step.caps]" , m .StepTypes )
529+ }
530+
531+ // TestManifestCapabilitiesInvalidFormat verifies that a plugin.json whose
532+ // "capabilities" field is neither an array nor an object (e.g. a bare string)
533+ // is rejected with a descriptive error.
534+ func TestManifestCapabilitiesInvalidFormat (t * testing.T ) {
535+ cases := []struct {
536+ name string
537+ json string
538+ }{
539+ {
540+ name : "string value" ,
541+ json : `{"name":"p","version":"1.0.0","author":"A","description":"D","capabilities":"oops"}` ,
542+ },
543+ {
544+ name : "numeric value" ,
545+ json : `{"name":"p","version":"1.0.0","author":"A","description":"D","capabilities":42}` ,
546+ },
547+ {
548+ name : "boolean value" ,
549+ json : `{"name":"p","version":"1.0.0","author":"A","description":"D","capabilities":true}` ,
550+ },
551+ }
552+ for _ , tc := range cases {
553+ t .Run (tc .name , func (t * testing.T ) {
554+ var m PluginManifest
555+ err := json .Unmarshal ([]byte (tc .json ), & m )
556+ if err == nil {
557+ t .Errorf ("expected error for capabilities %s, got nil" , tc .name )
558+ }
559+ })
510560 }
511561}
0 commit comments