internal/cli/kv_test.go (view raw)
1package cli
2
3import (
4 "fmt"
5 "path"
6 "strings"
7 "testing"
8 "time"
9
10 "postern/internal/db"
11)
12
13// newKVTestCommand sets up a DB-backed Command with a running HTTP service and
14// returns it. The service is stopped when the test finishes.
15func newKVTestCommand(t *testing.T) *Command {
16 t.Helper()
17 f := t.TempDir()
18
19 posternDB, err := db.OpenDB(&db.Config{
20 DBPath: path.Join(f, "meta.db"),
21 })
22 if err != nil {
23 t.Fatal(err)
24 }
25
26 c, err := New(&Config{
27 DB: posternDB,
28 })
29 if err != nil {
30 t.Fatal(err)
31 }
32
33 tempSocket := path.Join(f, "test.sock")
34 c.socketPath = tempSocket
35 c.httpClient = newUnixHTTPClient(tempSocket)
36
37 service := NewHTTPService(tempSocket, c)
38 go func() {
39 _ = service.Start()
40 }()
41
42 timeout := time.After(10 * time.Second)
43 ticker := time.NewTicker(100 * time.Millisecond)
44 defer ticker.Stop()
45 for service.Status() != "running" {
46 select {
47 case <-timeout:
48 t.Fatal("timed out waiting for http service to start")
49 case <-ticker.C:
50 }
51 }
52 t.Cleanup(func() { _ = service.Stop() })
53
54 return c
55}
56
57func TestKVListCommand(t *testing.T) {
58 c := newKVTestCommand(t)
59 ctx := t.Context()
60
61 seed := map[string]any{
62 "counter": 42,
63 "message": "hello world",
64 "a/b": "slash value",
65 }
66 for key, value := range seed {
67 if err := c.db.KVSet(ctx, key, value); err != nil {
68 t.Fatalf("seeding %q: %v", key, err)
69 }
70 }
71
72 out, err := executeCommand(c.rootCmd(), "kv", "list")
73 if err != nil {
74 t.Fatalf("kv list: %v", err)
75 }
76
77 for wantKey, wantValue := range seed {
78 if !strings.Contains(out, wantKey) {
79 t.Errorf("kv list output missing %q; got:\n%s", wantKey, out)
80 }
81 if !strings.Contains(out, fmt.Sprint(wantValue)) {
82 t.Errorf("kv list output missing %q; got:\n%s", wantValue, out)
83 }
84 }
85}
86
87func TestKVGetCommand(t *testing.T) {
88 c := newKVTestCommand(t)
89 ctx := t.Context()
90
91 if err := c.db.KVSet(ctx, "counter", 42); err != nil {
92 t.Fatal(err)
93 }
94 if err := c.db.KVSet(ctx, "a/b", "slash value"); err != nil {
95 t.Fatal(err)
96 }
97
98 // Plain key.
99 out, err := executeCommand(c.rootCmd(), "kv", "get", "counter")
100 if err != nil {
101 t.Fatalf("kv get counter: %v", err)
102 }
103 if got := strings.TrimSpace(out); got != "42" {
104 t.Errorf("kv get counter = %q, want %q", got, "42")
105 }
106
107 // Key containing a slash.
108 out, err = executeCommand(c.rootCmd(), "kv", "get", "a/b")
109 if err != nil {
110 t.Fatalf("kv get a/b: %v", err)
111 }
112 if got := strings.TrimSpace(out); got != "slash value" {
113 t.Errorf("kv get a/b = %q, want %q", got, "slash value")
114 }
115
116 // Missing key should error.
117 if _, err := executeCommand(c.rootCmd(), "kv", "get", "missing"); err == nil {
118 t.Error("kv get missing: expected error, got nil")
119 }
120}
121
122func TestKVPopCommand(t *testing.T) {
123 c := newKVTestCommand(t)
124 ctx := t.Context()
125
126 if err := c.db.KVSet(ctx, "counter", 42); err != nil {
127 t.Fatal(err)
128 }
129 if err := c.db.KVSet(ctx, "a/b", "slash value"); err != nil {
130 t.Fatal(err)
131 }
132
133 // Pop a plain key and confirm it returns the value and is deleted.
134 out, err := executeCommand(c.rootCmd(), "kv", "pop", "counter")
135 if err != nil {
136 t.Fatalf("kv pop counter: %v", err)
137 }
138 if got := strings.TrimSpace(out); got != "42" {
139 t.Errorf("kv pop counter = %q, want %q", got, "42")
140 }
141 if _, err := c.db.KVGet(ctx, "counter"); err == nil {
142 t.Error("counter still present after kv pop")
143 }
144
145 // Pop a key containing a slash.
146 out, err = executeCommand(c.rootCmd(), "kv", "pop", "a/b")
147 if err != nil {
148 t.Fatalf("kv pop a/b: %v", err)
149 }
150 if got := strings.TrimSpace(out); got != "slash value" {
151 t.Errorf("kv pop a/b = %q, want %q", got, "slash value")
152 }
153 if _, err := c.db.KVGet(ctx, "a/b"); err == nil {
154 t.Error("a/b still present after kv pop")
155 }
156
157 // Popping a missing key should error.
158 if _, err := executeCommand(c.rootCmd(), "kv", "pop", "missing"); err == nil {
159 t.Error("kv pop missing: expected error, got nil")
160 }
161}