| 📦 Turbo Python 6fc62ea k33g 9h ago | 1 | package pythonlang |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "os" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "rickub.com/turbo-editors/turbo-core/acp" |
| 10 | ) |
| 11 | |
| 12 | // The agents file Turbo Python writes. What the *feature* does is turbo-core's to |
| 13 | // test; what is checked here is the one part that belongs to this editor — |
| 14 | // that the starter file is filled in correctly, loads back as the agent it |
| 15 | // describes, and says enough for somebody to point it at their own. |
| 16 | |
| 17 | // readAgentsFile returns the agents file a project was given. |
| 18 | func readAgentsFile(t *testing.T, dir string) string { |
| 19 | t.Helper() |
| 20 | |
| 21 | data, err := os.ReadFile(acp.ProjectPath(Profile(), dir)) |
| 22 | if err != nil { |
| 23 | t.Fatalf("reading the agents file: %v", err) |
| 24 | } |
| 25 | return string(data) |
| 26 | } |
| 27 | |
| 28 | // createAgents writes the starter agents file into a fresh project. |
| 29 | func createAgents(t *testing.T) string { |
| 30 | t.Helper() |
| 31 | |
| 32 | dir := t.TempDir() |
| 33 | if _, err := acp.Create(Profile(), dir); err != nil { |
| 34 | t.Fatalf("acp.Create() error = %v", err) |
| 35 | } |
| 36 | return dir |
| 37 | } |
| 38 | |
| 39 | func TestTheCreatedAgentsFileFillsBothOfItsBlanks(t *testing.T) { |
| 40 | // Two different values — the project directory the example agent points |
| 41 | // into, and the user's own file that a comment names. Go writes |
| 42 | // %!s(MISSING) into the output rather than failing, so a miscounted verb |
| 43 | // produces a starter file that is written, opened, and wrong. |
| 44 | contents := readAgentsFile(t, createAgents(t)) |
| 45 | |
| 46 | if strings.Contains(contents, "%!") { |
| 47 | t.Errorf("the created file has an unfilled verb in it:\n%s", contents) |
| 48 | } |
| 49 | if want := Profile().ProjectDir() + "/agent.yaml"; !strings.Contains(contents, want) { |
| 50 | t.Errorf("the example agent does not point at %q:\n%s", want, contents) |
| 51 | } |
| 52 | if want := acp.UserPath(Profile()); want != "" && !strings.Contains(contents, want) { |
| 53 | t.Errorf("the created file never names the user's own file %q:\n%s", want, contents) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | func TestTheCreatedAgentsFileLoadsBackAsOneAgent(t *testing.T) { |
| 58 | // The file is mostly comments, and a comment carrying an [[agent]] example |
| 59 | // that the loader read as real would put an agent nobody configured into |
| 60 | // the menu. |
| 61 | dir := createAgents(t) |
| 62 | |
| 63 | list, err := acp.Load(Profile(), dir) |
| 64 | if err != nil { |
| 65 | t.Fatalf("acp.Load() error = %v", err) |
| 66 | } |
| 67 | if list.Len() != 1 { |
| 68 | t.Fatalf("the created file holds %d agents, want 1: %v", list.Len(), list.Agents()) |
| 69 | } |
| 70 | |
| 71 | agent := list.Agents()[0] |
| 72 | if agent.Command != "docker" { |
| 73 | t.Errorf("the example agent runs %q, want docker", agent.Command) |
| 74 | } |
| 75 | if want := "agent serve acp"; !strings.Contains(agent.CommandLine(), want) { |
| 76 | t.Errorf("the example command line is %q, want %q in it", agent.CommandLine(), want) |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | func TestTheCreatedAgentsFileExplainsItself(t *testing.T) { |
| 81 | // The keys, the window's keyboard and how to copy out of it are all |
| 82 | // invisible otherwise: this is the only document the editor hands a user. |
| 83 | contents := readAgentsFile(t, createAgents(t)) |
| 84 | |
| 85 | for _, want := range []string{ |
| 86 | "[[agent]]", "name", "command", "args", "env", "cwd", |
| 87 | "agentclientprotocol.com", |
| 88 | "Alt-Enter", "Ctrl-W", "Esc", "Ctrl-C", |
| 89 | } { |
| 90 | if !strings.Contains(contents, want) { |
| 91 | t.Errorf("the created file never mentions %q:\n%s", want, contents) |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | func TestTheCreatedAgentsFileNamesThisEditorsOwnLanguage(t *testing.T) { |
| 97 | // The comment about coloured code blocks is the one line of this file that |
| 98 | // is about Turbo Python rather than about the protocol, and a copy left naming |
| 99 | // another editor's language would be the obvious way to get it wrong. |
| 100 | contents := readAgentsFile(t, createAgents(t)) |
| 101 | |
| 102 | if want := "```python"; !strings.Contains(contents, want) { |
| 103 | t.Errorf("the created file never mentions a %q fence:\n%s", want, contents) |
| 104 | } |
| 105 | if want := ".py files"; !strings.Contains(contents, want) { |
| 106 | t.Errorf("the created file never mentions %q:\n%s", want, contents) |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | func TestCreatingAgentsTwiceLeavesTheFirstAlone(t *testing.T) { |
| 111 | dir := createAgents(t) |
| 112 | path := acp.ProjectPath(Profile(), dir) |
| 113 | |
| 114 | if err := os.WriteFile(path, []byte("# mine\n"), 0o644); err != nil { |
| 115 | t.Fatalf("writing over it: %v", err) |
| 116 | } |
| 117 | if _, err := acp.Create(Profile(), dir); !errors.Is(err, acp.ErrExists) { |
| 118 | t.Errorf("acp.Create() error = %v, want ErrExists", err) |
| 119 | } |
| 120 | |
| 121 | data, err := os.ReadFile(path) |
| 122 | if err != nil { |
| 123 | t.Fatalf("reading it back: %v", err) |
| 124 | } |
| 125 | if string(data) != "# mine\n" { |
| 126 | t.Errorf("the file was overwritten: %q", data) |
| 127 | } |
| 128 | } |