mirror of
https://github.com/larksuite/cli.git
synced 2026-07-08 18:13:01 +08:00
Remove redundant skill documentation (pre-check table, validation error examples, JSON return samples, fullstack-cli references) that duplicate CLI error hints. Make --plugin version optional and resolve from package.json actionPlugins. Drop unused createdBy field.
293 lines
8.9 KiB
Go
293 lines
8.9 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package apps
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
)
|
|
|
|
func TestPluginInstanceCreate_Basic(t *testing.T) {
|
|
dir := setupPluginTestProjectWithManifest(t, "server", "@test/my-plugin")
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--plugin", "@test/my-plugin@1.0.0",
|
|
"--name", "My Instance",
|
|
"--form-value", `{"prompt":"hello"}`,
|
|
"--project-path", dir,
|
|
"--format", "json",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var env map[string]interface{}
|
|
if err := json.Unmarshal(stdout.Bytes(), &env); err != nil {
|
|
t.Fatalf("invalid JSON output: %v", err)
|
|
}
|
|
data, _ := env["data"].(map[string]interface{})
|
|
if data["id"] != "test-my-plugin" {
|
|
t.Errorf("id = %v, want test-my-plugin (auto-derived)", data["id"])
|
|
}
|
|
if data["pluginKey"] != "@test/my-plugin" {
|
|
t.Errorf("pluginKey = %v, want @test/my-plugin", data["pluginKey"])
|
|
}
|
|
|
|
// Verify file was written
|
|
capPath := filepath.Join(dir, "server", "capabilities", "test-my-plugin.json")
|
|
capData, err := os.ReadFile(capPath) //nolint:forbidigo
|
|
if err != nil {
|
|
t.Fatalf("capability file not created: %v", err)
|
|
}
|
|
var cap map[string]interface{}
|
|
if err := json.Unmarshal(capData, &cap); err != nil {
|
|
t.Fatalf("invalid capability JSON: %v", err)
|
|
}
|
|
if cap["name"] != "My Instance" {
|
|
t.Errorf("cap.name = %v, want My Instance", cap["name"])
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_CustomID(t *testing.T) {
|
|
dir := setupPluginTestProjectWithManifest(t, "server", "@test/my-plugin")
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--id", "custom-summary",
|
|
"--plugin", "@test/my-plugin@2.0.0",
|
|
"--name", "Custom",
|
|
"--form-value", `{"key":"val"}`,
|
|
"--project-path", dir,
|
|
"--format", "json",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
capPath := filepath.Join(dir, "server", "capabilities", "custom-summary.json")
|
|
if _, err := os.Stat(capPath); err != nil { //nolint:forbidigo
|
|
t.Fatalf("capability file not created at custom id path: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_WithParamsSchema(t *testing.T) {
|
|
dir := setupPluginTestProjectWithManifest(t, "server", "@test/my-plugin")
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--plugin", "@test/my-plugin@1.0.0",
|
|
"--name", "WithSchema",
|
|
"--form-value", `{"prompt":"{{input.text}}"}`,
|
|
"--params-schema", `{"type":"object","properties":{"text":{"type":"string","description":"user input text"}}}`,
|
|
"--project-path", dir,
|
|
"--format", "json",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
capPath := filepath.Join(dir, "server", "capabilities", "test-my-plugin.json")
|
|
capData, err := os.ReadFile(capPath) //nolint:forbidigo
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var cap map[string]interface{}
|
|
if err := json.Unmarshal(capData, &cap); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := cap["paramsSchema"]; !ok {
|
|
t.Error("paramsSchema should be present in capability")
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_DuplicateID(t *testing.T) {
|
|
dir := setupPluginTestProjectWithManifest(t, "server", "@test/my-plugin")
|
|
capDir := filepath.Join(dir, "server", "capabilities")
|
|
writeTestCapJSON(t, capDir, "existing.json", map[string]interface{}{"id": "existing"})
|
|
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--id", "existing",
|
|
"--plugin", "@test/my-plugin@1.0.0",
|
|
"--name", "Dup",
|
|
"--form-value", `{}`,
|
|
"--project-path", dir,
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err == nil {
|
|
t.Fatal("expected error for duplicate id")
|
|
}
|
|
p, ok := errs.ProblemOf(err)
|
|
if !ok {
|
|
t.Fatalf("expected typed error, got %T: %v", err, err)
|
|
}
|
|
if p.Subtype != errs.SubtypeInvalidArgument {
|
|
t.Errorf("subtype = %q, want invalid_argument", p.Subtype)
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_ForceOverwrite(t *testing.T) {
|
|
dir := setupPluginTestProjectWithManifest(t, "server", "@test/my-plugin")
|
|
capDir := filepath.Join(dir, "server", "capabilities")
|
|
writeTestCapJSON(t, capDir, "existing.json", map[string]interface{}{"id": "existing", "name": "Old"})
|
|
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--id", "existing",
|
|
"--plugin", "@test/my-plugin@1.0.0",
|
|
"--name", "New",
|
|
"--form-value", `{}`,
|
|
"--force",
|
|
"--project-path", dir,
|
|
"--format", "json",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error with --force: %v", err)
|
|
}
|
|
|
|
capData, _ := os.ReadFile(filepath.Join(capDir, "existing.json")) //nolint:forbidigo
|
|
var cap map[string]interface{}
|
|
json.Unmarshal(capData, &cap)
|
|
if cap["name"] != "New" {
|
|
t.Errorf("name = %v, want New (overwritten)", cap["name"])
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_PluginNotInstalled(t *testing.T) {
|
|
dir := setupPluginTestProject(t, "server")
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--plugin", "@test/not-installed@1.0.0",
|
|
"--name", "Fail",
|
|
"--form-value", `{}`,
|
|
"--project-path", dir,
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err == nil {
|
|
t.Fatal("expected error when plugin not installed")
|
|
}
|
|
p, ok := errs.ProblemOf(err)
|
|
if !ok {
|
|
t.Fatalf("expected typed error, got %T: %v", err, err)
|
|
}
|
|
if p.Subtype != errs.SubtypeFailedPrecondition {
|
|
t.Errorf("subtype = %q, want failed_precondition", p.Subtype)
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_InvalidPluginFormat(t *testing.T) {
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
dir := setupPluginTestProject(t, "server")
|
|
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--plugin", "no-version",
|
|
"--name", "Fail",
|
|
"--form-value", `{}`,
|
|
"--project-path", dir,
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid plugin format")
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_InvalidJSON(t *testing.T) {
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
dir := setupPluginTestProject(t, "server")
|
|
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--plugin", "@test/p@1.0.0",
|
|
"--name", "Fail",
|
|
"--form-value", `not json`,
|
|
"--project-path", dir,
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid JSON")
|
|
}
|
|
}
|
|
|
|
func TestPluginInstanceCreate_AutoCreateCapDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0o644); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, ".env.local"), []byte("MIAODA_APP_TYPE=2\n"), 0o644); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
pluginKey := "@test/my-plugin"
|
|
manifestDir := filepath.Join(dir, "node_modules", pluginKey)
|
|
if err := os.MkdirAll(manifestDir, 0o755); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(manifestDir, "manifest.json"), []byte(`{}`), 0o644); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
writeTestPkgJSON(t, dir, map[string]interface{}{
|
|
"actionPlugins": map[string]interface{}{
|
|
pluginKey: "1.0.0",
|
|
},
|
|
})
|
|
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
err := runAppsShortcut(t, AppsPluginInstanceCreate, []string{
|
|
"+plugin-instance-create",
|
|
"--plugin", "@test/my-plugin@1.0.0",
|
|
"--name", "AutoDir",
|
|
"--form-value", `{}`,
|
|
"--project-path", dir,
|
|
"--format", "json",
|
|
"--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("should auto-create capabilities dir: %v", err)
|
|
}
|
|
|
|
capPath := filepath.Join(dir, "server", "capabilities", "test-my-plugin.json")
|
|
if _, err := os.Stat(capPath); err != nil { //nolint:forbidigo
|
|
t.Fatalf("capability file not created: %v", err)
|
|
}
|
|
}
|
|
|
|
// --- helpers ---
|
|
|
|
// setupPluginTestProjectWithManifest creates a project dir with package.json,
|
|
// capabilities dir, and a minimal manifest.json for the given plugin key.
|
|
func setupPluginTestProjectWithManifest(t *testing.T, appType, pluginKey string) string {
|
|
t.Helper()
|
|
dir := setupPluginTestProject(t, appType)
|
|
manifestDir := filepath.Join(dir, "node_modules", pluginKey)
|
|
if err := os.MkdirAll(manifestDir, 0o755); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(manifestDir, "manifest.json"), []byte(`{"actions":[]}`), 0o644); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
// Register the plugin in actionPlugins so create's actionPlugins check passes
|
|
writeTestPkgJSON(t, dir, map[string]interface{}{
|
|
"actionPlugins": map[string]interface{}{
|
|
pluginKey: "1.0.0",
|
|
},
|
|
})
|
|
return dir
|
|
}
|