package main import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" ) func TestWorkerReloadCopiesReadOnlySkillAndExecutes(t *testing.T) { skills := t.TempDir() work := t.TempDir() dir := filepath.Join(skills, "demo") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } manifestJSON := `{ "protocol":"jarvis.skill.v1", "id":"test.remote", "name":"Remote test", "version":"1.0.0", "runtime":{"type":"process","command":"./run","timeout_ms":5000}, "actions":[{ "name":"echo", "description":"echo", "input_schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"],"additionalProperties":false}, "output_schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"],"additionalProperties":false} }] }` if err := os.WriteFile(filepath.Join(dir, "skill.json"), []byte(manifestJSON), 0o644); err != nil { t.Fatal(err) } run := `#!/bin/sh python3 -c 'import json,sys; r=json.load(sys.stdin); json.dump({"protocol":"jarvis.skill.invoke.v1","success":True,"data":{"text":r["input"]["text"]}},sys.stdout)' ` if err := os.WriteFile(filepath.Join(dir, "run"), []byte(run), 0o755); err != nil { t.Fatal(err) } w := &worker{skillsDir: skills, workDir: work, maxOutput: 1 << 20, runtime: "test", skills: map[string]manifest{}, actions: map[string]skillRef{}} if err := w.reload(); err != nil { t.Fatal(err) } ref, ok := w.actions["test.remote\x00echo"] if !ok { t.Fatalf("action not loaded") } if filepath.Dir(ref.Dir) != work { t.Fatalf("skill did not execute from isolated work copy: %s", ref.Dir) } res := w.execute(context.Background(), ref, invokeRequest{Protocol: invokeProtocol, RequestID: "x", SkillID: "test.remote", Action: "echo", Input: map[string]any{"text": "hello"}, Context: invokeContext{Timezone: "UTC"}}) if !res.Success { t.Fatalf("execution failed: %+v", res) } b, _ := json.Marshal(res.Data) if string(b) != `{"text":"hello"}` { t.Fatalf("data=%s", b) } } func TestWorkerEnvironmentExplicitEnvFrom(t *testing.T) { t.Setenv("HUE_APP_KEY", "secret-key") t.Setenv("UNRELATED_SECRET", "must-not-leak") w := &worker{runtime: "python"} ref := skillRef{Manifest: manifest{ID: "test.env", Runtime: runtimeSpec{EnvFrom: []string{"HUE_APP_KEY", "JARVIS_MESH_ENROLLMENT_TOKEN"}}}, Action: actionManifest{Name: "run"}} env := w.environment(ref, invokeRequest{RequestID: "req", Context: invokeContext{Timezone: "Europe/Berlin"}}) got := map[string]string{} for _, item := range env { parts := strings.SplitN(item, "=", 2) if len(parts) == 2 { got[parts[0]] = parts[1] } } if got["HUE_APP_KEY"] != "secret-key" { t.Fatalf("explicit env_from was not passed: %#v", got) } if _, ok := got["UNRELATED_SECRET"]; ok { t.Fatalf("unrelated parent secret leaked into skill env") } if _, ok := got["JARVIS_MESH_ENROLLMENT_TOKEN"]; ok { t.Fatalf("JARVIS control env must never be passed to skills") } } func TestHomeControlSkillPackManifestsLoad(t *testing.T) { skillsDir := filepath.Clean(filepath.Join("..", "..", "skills", "python")) w := &worker{skillsDir: skillsDir, maxOutput: 1 << 20, runtime: "python", skills: map[string]manifest{}, actions: map[string]skillRef{}} if err := w.reload(); err != nil { t.Fatalf("home-control skill pack did not load: %v", err) } for _, id := range []string{"philips.hue", "unifi.network", "unifi.protect", "proxmox.ve", "dockge.compose", "home.network-tools", "notify.ntfy"} { if _, ok := w.skills[id]; !ok { t.Fatalf("expected skill %s to be loaded", id) } } if len(w.actions) < 42 { t.Fatalf("expected home-control actions, got %d", len(w.actions)) } }