mirror of
https://github.com/foomo/posh.git
synced 2026-10-04 14:06:58 +00:00
107 lines
3.9 KiB
Go
107 lines
3.9 KiB
Go
package command_test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/foomo/posh/pkg/cache"
|
|
"github.com/foomo/posh/pkg/command"
|
|
"github.com/foomo/posh/pkg/log"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// skiller is a command contributing extra SKILL.md markdown. It writes its prose
|
|
// against the name it is given, as a command registered under a non-default name
|
|
// must.
|
|
type skiller struct{ leaf }
|
|
|
|
func (skiller) Skill(ctx context.Context, name string) string {
|
|
return "#### Notes\n\nRun `" + name + "` twice."
|
|
}
|
|
|
|
// metadataer is a command supplying its own skill frontmatter.
|
|
type metadataer struct{ leaf }
|
|
|
|
func (metadataer) SkillMetadata(ctx context.Context, name string) command.SkillMetadata {
|
|
return command.SkillMetadata{Description: "Use when " + name + " is needed."}
|
|
}
|
|
|
|
// TestSkill_NotASkiller covers the fallback: a command that does not opt in
|
|
// contributes nothing.
|
|
func TestSkill_NotASkiller(t *testing.T) {
|
|
assert.Empty(t, command.Skill(t.Context(), leaf{}, "leaf"))
|
|
}
|
|
|
|
// TestSkill_Skiller covers the dispatch, and that the registered name reaches
|
|
// the contribution: the same command can be registered under another name, and
|
|
// prose naming a command that does not exist in the project is worse than none.
|
|
func TestSkill_Skiller(t *testing.T) {
|
|
assert.Equal(t, "#### Notes\n\nRun `admiral` twice.",
|
|
command.Skill(t.Context(), skiller{}, "admiral"))
|
|
}
|
|
|
|
// TestSkillMetadataOf covers the per-command frontmatter, and the zero value a
|
|
// command that does not opt in falls back to.
|
|
func TestSkillMetadataOf(t *testing.T) {
|
|
assert.Equal(t,
|
|
command.SkillMetadata{Description: "Use when admiral is needed."},
|
|
command.SkillMetadataOf(t.Context(), metadataer{}, "admiral"))
|
|
|
|
assert.Equal(t, command.SkillMetadata{}, command.SkillMetadataOf(t.Context(), leaf{}, "leaf"))
|
|
}
|
|
|
|
// TestSkill_NoSelfDefeatingBuiltIns covers the two deliberate omissions. Both
|
|
// commands' prose would only steer an agent away from them - exit does nothing
|
|
// under `posh execute`, and the catalog beats help - and a skill has to be
|
|
// loaded before an agent can read that, so contributing none is cheaper.
|
|
func TestSkill_NoSelfDefeatingBuiltIns(t *testing.T) {
|
|
l := log.NewTest(t)
|
|
|
|
assert.Empty(t, command.Skill(t.Context(), command.NewExit(l), "exit"))
|
|
assert.Empty(t, command.Skill(t.Context(), command.NewHelp(l, command.Commands{}), "help"))
|
|
}
|
|
|
|
// TestSkill_BuiltIns asserts the remaining built-ins contribute prose, and that
|
|
// each contribution is renderable: a heading above level 4 would collide with
|
|
// the command heading RenderCommandSkill puts above it.
|
|
func TestSkill_BuiltIns(t *testing.T) {
|
|
l := log.NewTest(t)
|
|
|
|
for name, cmd := range map[string]any{
|
|
"cache": command.NewCache(l, cache.NewMemoryCache()),
|
|
"check": command.NewCheck(l),
|
|
"env": command.NewEnv(l),
|
|
"history": command.NewHistory(l, nil),
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
actual := command.Skill(t.Context(), cmd, name)
|
|
|
|
require.NotEmpty(t, actual, "every built-in should contribute skill prose")
|
|
assert.True(t, strings.HasPrefix(actual, "#### "),
|
|
"contributions must start at heading level 4, below the command's own")
|
|
|
|
for line := range strings.SplitSeq(actual, "\n") {
|
|
assert.False(t, strings.HasPrefix(line, "### ") || strings.HasPrefix(line, "## "),
|
|
"a heading above level 4 would collide with the command heading: %q", line)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDescribe_Help covers the hand-built CommandInfo: help is not tree based,
|
|
// so without it the optional [command] argument would be invisible.
|
|
func TestDescribe_Help(t *testing.T) {
|
|
actual := command.NewHelp(log.NewTest(t), command.Commands{}).Describe(t.Context())
|
|
|
|
assert.Equal(t, "help", actual.FullPath)
|
|
|
|
require.Len(t, actual.Arguments, 1)
|
|
assert.Equal(t, "command", actual.Arguments[0].Name)
|
|
assert.True(t, actual.Arguments[0].Optional)
|
|
|
|
// Usage is rebuilt from the arguments, so an optional one is angled.
|
|
assert.Equal(t, "<command>", actual.Usage())
|
|
}
|