Skip to content

Commit 7435745

Browse files
committed
Add tests for review-identified multi-client bugs
Cover legacy no-op with explicit client, upgrade extracting to all existing clients, and multi-client rollback and error paths.
1 parent b23c66e commit 7435745

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

pkg/skills/skillsvc/skillsvc_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,79 @@ func TestInstallWithExtraction(t *testing.T) {
734734
assert.Equal(t, http.StatusConflict, httperr.Code(err))
735735
assert.Contains(t, err.Error(), "not managed by ToolHive")
736736
})
737+
738+
t.Run("legacy row with explicit client is not a no-op", func(t *testing.T) {
739+
t.Parallel()
740+
ctrl := gomock.NewController(t)
741+
store := storemocks.NewMockSkillStore(ctrl)
742+
pr := skillsmocks.NewMockPathResolver(ctrl)
743+
inst := skillsmocks.NewMockInstaller(ctrl)
744+
745+
dirB := filepath.Join(t.TempDir(), "b", "my-skill")
746+
existing := skills.InstalledSkill{
747+
Metadata: skills.SkillMetadata{Name: "my-skill"},
748+
Digest: "sha256:abc",
749+
Clients: []string{},
750+
}
751+
pr.EXPECT().GetSkillPath("opencode", "my-skill", skills.ScopeUser, "").Return(dirB, nil)
752+
store.EXPECT().Get(gomock.Any(), "my-skill", skills.ScopeUser, "").Return(existing, nil)
753+
inst.EXPECT().Extract(layerData, dirB, false).
754+
Return(&skills.ExtractResult{SkillDir: dirB, Files: 1}, nil)
755+
store.EXPECT().Update(gomock.Any(), gomock.Any()).DoAndReturn(
756+
func(_ context.Context, sk skills.InstalledSkill) error {
757+
assert.Contains(t, sk.Clients, "opencode")
758+
return nil
759+
})
760+
761+
svc := New(store, WithPathResolver(pr), WithInstaller(inst))
762+
result, err := svc.Install(t.Context(), skills.InstallOptions{
763+
Name: "my-skill",
764+
LayerData: layerData,
765+
Digest: "sha256:abc",
766+
Clients: []string{"opencode"},
767+
})
768+
require.NoError(t, err)
769+
assert.Equal(t, "my-skill", result.Skill.Metadata.Name)
770+
})
771+
772+
t.Run("upgrade extracts to all existing clients not just requested", func(t *testing.T) {
773+
t.Parallel()
774+
ctrl := gomock.NewController(t)
775+
store := storemocks.NewMockSkillStore(ctrl)
776+
pr := skillsmocks.NewMockPathResolver(ctrl)
777+
inst := skillsmocks.NewMockInstaller(ctrl)
778+
779+
dirA := filepath.Join(t.TempDir(), "a", "my-skill")
780+
dirB := filepath.Join(t.TempDir(), "b", "my-skill")
781+
existing := skills.InstalledSkill{
782+
Metadata: skills.SkillMetadata{Name: "my-skill"},
783+
Digest: "sha256:old",
784+
Clients: []string{"claude-code"},
785+
}
786+
pr.EXPECT().GetSkillPath("opencode", "my-skill", skills.ScopeUser, "").Return(dirB, nil)
787+
pr.EXPECT().GetSkillPath("claude-code", "my-skill", skills.ScopeUser, "").Return(dirA, nil)
788+
store.EXPECT().Get(gomock.Any(), "my-skill", skills.ScopeUser, "").Return(existing, nil)
789+
inst.EXPECT().Extract(layerData, dirB, true).
790+
Return(&skills.ExtractResult{SkillDir: dirB, Files: 1}, nil)
791+
inst.EXPECT().Extract(layerData, dirA, true).
792+
Return(&skills.ExtractResult{SkillDir: dirA, Files: 1}, nil)
793+
store.EXPECT().Update(gomock.Any(), gomock.Any()).DoAndReturn(
794+
func(_ context.Context, sk skills.InstalledSkill) error {
795+
assert.ElementsMatch(t, []string{"opencode", "claude-code"}, sk.Clients)
796+
assert.Equal(t, "sha256:new", sk.Digest)
797+
return nil
798+
})
799+
800+
svc := New(store, WithPathResolver(pr), WithInstaller(inst))
801+
result, err := svc.Install(t.Context(), skills.InstallOptions{
802+
Name: "my-skill",
803+
LayerData: layerData,
804+
Digest: "sha256:new",
805+
Clients: []string{"opencode"},
806+
})
807+
require.NoError(t, err)
808+
assert.ElementsMatch(t, []string{"opencode", "claude-code"}, result.Skill.Clients)
809+
})
737810
}
738811

739812
// buildTestArtifact creates a real OCI skill artifact in the store and returns

0 commit comments

Comments
 (0)