ScuttleBot

scuttlebot / internal / api / config_handlers_test.go
Source Blame History 473 lines
17e2c1d… lmata 1 package api
17e2c1d… lmata 2
17e2c1d… lmata 3 import (
17e2c1d… lmata 4 "bytes"
17e2c1d… lmata 5 "encoding/json"
17e2c1d… lmata 6 "io"
17e2c1d… lmata 7 "log/slog"
17e2c1d… lmata 8 "net/http"
17e2c1d… lmata 9 "net/http/httptest"
17e2c1d… lmata 10 "path/filepath"
17e2c1d… lmata 11 "testing"
763c873… lmata 12 "time"
17e2c1d… lmata 13
68677f9… noreply 14 "github.com/conflicthq/scuttlebot/internal/auth"
17e2c1d… lmata 15 "github.com/conflicthq/scuttlebot/internal/config"
17e2c1d… lmata 16 "github.com/conflicthq/scuttlebot/internal/registry"
17e2c1d… lmata 17 )
17e2c1d… lmata 18
17e2c1d… lmata 19 func newCfgTestServer(t *testing.T) (*httptest.Server, *ConfigStore) {
17e2c1d… lmata 20 t.Helper()
17e2c1d… lmata 21 dir := t.TempDir()
17e2c1d… lmata 22 path := filepath.Join(dir, "scuttlebot.yaml")
17e2c1d… lmata 23
17e2c1d… lmata 24 var cfg config.Config
17e2c1d… lmata 25 cfg.Defaults()
17e2c1d… lmata 26 cfg.Ergo.DataDir = dir
17e2c1d… lmata 27
17e2c1d… lmata 28 store := NewConfigStore(path, cfg)
17e2c1d… lmata 29 reg := registry.New(nil, []byte("key"))
17e2c1d… lmata 30 log := slog.New(slog.NewTextHandler(io.Discard, nil))
68677f9… noreply 31 srv := httptest.NewServer(New(reg, auth.TestStore("tok"), nil, nil, nil, nil, nil, store, "", log).Handler())
17e2c1d… lmata 32 t.Cleanup(srv.Close)
17e2c1d… lmata 33 return srv, store
17e2c1d… lmata 34 }
17e2c1d… lmata 35
17e2c1d… lmata 36 func TestHandleGetConfig(t *testing.T) {
17e2c1d… lmata 37 srv, _ := newCfgTestServer(t)
17e2c1d… lmata 38
17e2c1d… lmata 39 req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/config", nil)
17e2c1d… lmata 40 req.Header.Set("Authorization", "Bearer tok")
17e2c1d… lmata 41 resp, err := http.DefaultClient.Do(req)
17e2c1d… lmata 42 if err != nil {
17e2c1d… lmata 43 t.Fatal(err)
17e2c1d… lmata 44 }
17e2c1d… lmata 45 defer resp.Body.Close()
17e2c1d… lmata 46
17e2c1d… lmata 47 if resp.StatusCode != http.StatusOK {
17e2c1d… lmata 48 t.Fatalf("want 200, got %d", resp.StatusCode)
17e2c1d… lmata 49 }
17e2c1d… lmata 50 var body map[string]any
17e2c1d… lmata 51 if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
17e2c1d… lmata 52 t.Fatal(err)
17e2c1d… lmata 53 }
17e2c1d… lmata 54 if _, ok := body["bridge"]; !ok {
17e2c1d… lmata 55 t.Error("response missing bridge section")
17e2c1d… lmata 56 }
17e2c1d… lmata 57 if _, ok := body["topology"]; !ok {
17e2c1d… lmata 58 t.Error("response missing topology section")
17e2c1d… lmata 59 }
17e2c1d… lmata 60 }
17e2c1d… lmata 61
17e2c1d… lmata 62 func TestHandlePutConfig(t *testing.T) {
17e2c1d… lmata 63 srv, store := newCfgTestServer(t)
17e2c1d… lmata 64
17e2c1d… lmata 65 update := map[string]any{
17e2c1d… lmata 66 "bridge": map[string]any{
17e2c1d… lmata 67 "web_user_ttl_minutes": 10,
17e2c1d… lmata 68 },
17e2c1d… lmata 69 "topology": map[string]any{
17e2c1d… lmata 70 "nick": "topo-bot",
17e2c1d… lmata 71 "channels": []map[string]any{
17e2c1d… lmata 72 {"name": "#general", "topic": "Fleet"},
17e2c1d… lmata 73 },
17e2c1d… lmata 74 },
17e2c1d… lmata 75 }
17e2c1d… lmata 76 body, _ := json.Marshal(update)
17e2c1d… lmata 77 req, _ := http.NewRequest(http.MethodPut, srv.URL+"/v1/config", bytes.NewReader(body))
17e2c1d… lmata 78 req.Header.Set("Authorization", "Bearer tok")
17e2c1d… lmata 79 req.Header.Set("Content-Type", "application/json")
17e2c1d… lmata 80 resp, err := http.DefaultClient.Do(req)
17e2c1d… lmata 81 if err != nil {
17e2c1d… lmata 82 t.Fatal(err)
17e2c1d… lmata 83 }
17e2c1d… lmata 84 defer resp.Body.Close()
17e2c1d… lmata 85
17e2c1d… lmata 86 if resp.StatusCode != http.StatusOK {
17e2c1d… lmata 87 t.Fatalf("want 200, got %d", resp.StatusCode)
17e2c1d… lmata 88 }
17e2c1d… lmata 89 var result struct {
17e2c1d… lmata 90 Saved bool `json:"saved"`
17e2c1d… lmata 91 }
17e2c1d… lmata 92 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
17e2c1d… lmata 93 t.Fatal(err)
17e2c1d… lmata 94 }
17e2c1d… lmata 95 if !result.Saved {
17e2c1d… lmata 96 t.Error("expected saved=true")
17e2c1d… lmata 97 }
17e2c1d… lmata 98
17e2c1d… lmata 99 // Verify in-memory state updated.
17e2c1d… lmata 100 got := store.Get()
17e2c1d… lmata 101 if got.Bridge.WebUserTTLMinutes != 10 {
17e2c1d… lmata 102 t.Errorf("bridge.web_user_ttl_minutes = %d, want 10", got.Bridge.WebUserTTLMinutes)
17e2c1d… lmata 103 }
17e2c1d… lmata 104 if got.Topology.Nick != "topo-bot" {
17e2c1d… lmata 105 t.Errorf("topology.nick = %q, want topo-bot", got.Topology.Nick)
17e2c1d… lmata 106 }
17e2c1d… lmata 107 if len(got.Topology.Channels) != 1 || got.Topology.Channels[0].Name != "#general" {
17e2c1d… lmata 108 t.Errorf("topology.channels = %+v", got.Topology.Channels)
763c873… lmata 109 }
763c873… lmata 110 }
763c873… lmata 111
763c873… lmata 112 func TestHandlePutConfigAgentPolicy(t *testing.T) {
763c873… lmata 113 srv, store := newCfgTestServer(t)
763c873… lmata 114
763c873… lmata 115 update := map[string]any{
763c873… lmata 116 "agent_policy": map[string]any{
763c873… lmata 117 "require_checkin": true,
763c873… lmata 118 "checkin_channel": "#fleet",
763c873… lmata 119 "required_channels": []string{"#general"},
763c873… lmata 120 },
763c873… lmata 121 }
763c873… lmata 122 body, _ := json.Marshal(update)
763c873… lmata 123 req, _ := http.NewRequest(http.MethodPut, srv.URL+"/v1/config", bytes.NewReader(body))
763c873… lmata 124 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 125 req.Header.Set("Content-Type", "application/json")
763c873… lmata 126 resp, err := http.DefaultClient.Do(req)
763c873… lmata 127 if err != nil {
763c873… lmata 128 t.Fatal(err)
763c873… lmata 129 }
763c873… lmata 130 defer resp.Body.Close()
763c873… lmata 131 if resp.StatusCode != http.StatusOK {
763c873… lmata 132 t.Fatalf("want 200, got %d", resp.StatusCode)
763c873… lmata 133 }
763c873… lmata 134
763c873… lmata 135 got := store.Get()
763c873… lmata 136 if !got.AgentPolicy.RequireCheckin {
763c873… lmata 137 t.Error("agent_policy.require_checkin should be true")
763c873… lmata 138 }
763c873… lmata 139 if got.AgentPolicy.CheckinChannel != "#fleet" {
763c873… lmata 140 t.Errorf("agent_policy.checkin_channel = %q, want #fleet", got.AgentPolicy.CheckinChannel)
763c873… lmata 141 }
763c873… lmata 142 if len(got.AgentPolicy.RequiredChannels) != 1 || got.AgentPolicy.RequiredChannels[0] != "#general" {
763c873… lmata 143 t.Errorf("agent_policy.required_channels = %v", got.AgentPolicy.RequiredChannels)
763c873… lmata 144 }
763c873… lmata 145 }
763c873… lmata 146
763c873… lmata 147 func TestHandlePutConfigLogging(t *testing.T) {
763c873… lmata 148 srv, store := newCfgTestServer(t)
763c873… lmata 149
763c873… lmata 150 update := map[string]any{
763c873… lmata 151 "logging": map[string]any{
336984b… lmata 152 "enabled": true,
336984b… lmata 153 "dir": "./data/logs",
336984b… lmata 154 "format": "jsonl",
336984b… lmata 155 "rotation": "daily",
336984b… lmata 156 "per_channel": true,
763c873… lmata 157 "max_age_days": 30,
763c873… lmata 158 },
763c873… lmata 159 }
763c873… lmata 160 body, _ := json.Marshal(update)
763c873… lmata 161 req, _ := http.NewRequest(http.MethodPut, srv.URL+"/v1/config", bytes.NewReader(body))
763c873… lmata 162 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 163 req.Header.Set("Content-Type", "application/json")
763c873… lmata 164 resp, err := http.DefaultClient.Do(req)
763c873… lmata 165 if err != nil {
763c873… lmata 166 t.Fatal(err)
763c873… lmata 167 }
763c873… lmata 168 defer resp.Body.Close()
763c873… lmata 169 if resp.StatusCode != http.StatusOK {
763c873… lmata 170 t.Fatalf("want 200, got %d", resp.StatusCode)
763c873… lmata 171 }
763c873… lmata 172
763c873… lmata 173 got := store.Get()
763c873… lmata 174 if !got.Logging.Enabled {
763c873… lmata 175 t.Error("logging.enabled should be true")
763c873… lmata 176 }
763c873… lmata 177 if got.Logging.Dir != "./data/logs" {
763c873… lmata 178 t.Errorf("logging.dir = %q, want ./data/logs", got.Logging.Dir)
763c873… lmata 179 }
763c873… lmata 180 if got.Logging.Format != "jsonl" {
763c873… lmata 181 t.Errorf("logging.format = %q, want jsonl", got.Logging.Format)
763c873… lmata 182 }
763c873… lmata 183 if got.Logging.Rotation != "daily" {
763c873… lmata 184 t.Errorf("logging.rotation = %q, want daily", got.Logging.Rotation)
763c873… lmata 185 }
763c873… lmata 186 if !got.Logging.PerChannel {
763c873… lmata 187 t.Error("logging.per_channel should be true")
763c873… lmata 188 }
763c873… lmata 189 if got.Logging.MaxAgeDays != 30 {
763c873… lmata 190 t.Errorf("logging.max_age_days = %d, want 30", got.Logging.MaxAgeDays)
763c873… lmata 191 }
763c873… lmata 192 }
763c873… lmata 193
763c873… lmata 194 func TestHandlePutConfigErgo(t *testing.T) {
763c873… lmata 195 srv, store := newCfgTestServer(t)
763c873… lmata 196
763c873… lmata 197 update := map[string]any{
763c873… lmata 198 "ergo": map[string]any{
763c873… lmata 199 "network_name": "testnet",
763c873… lmata 200 "server_name": "irc.test.local",
763c873… lmata 201 },
763c873… lmata 202 }
763c873… lmata 203 body, _ := json.Marshal(update)
763c873… lmata 204 req, _ := http.NewRequest(http.MethodPut, srv.URL+"/v1/config", bytes.NewReader(body))
763c873… lmata 205 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 206 req.Header.Set("Content-Type", "application/json")
763c873… lmata 207 resp, err := http.DefaultClient.Do(req)
763c873… lmata 208 if err != nil {
763c873… lmata 209 t.Fatal(err)
763c873… lmata 210 }
763c873… lmata 211 defer resp.Body.Close()
763c873… lmata 212 if resp.StatusCode != http.StatusOK {
763c873… lmata 213 t.Fatalf("want 200, got %d", resp.StatusCode)
763c873… lmata 214 }
763c873… lmata 215
763c873… lmata 216 // Ergo changes should be flagged as restart_required.
763c873… lmata 217 var result struct {
763c873… lmata 218 Saved bool `json:"saved"`
763c873… lmata 219 RestartRequired []string `json:"restart_required"`
763c873… lmata 220 }
763c873… lmata 221 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
763c873… lmata 222 t.Fatal(err)
763c873… lmata 223 }
763c873… lmata 224 if !result.Saved {
763c873… lmata 225 t.Error("expected saved=true")
763c873… lmata 226 }
763c873… lmata 227 if len(result.RestartRequired) == 0 {
763c873… lmata 228 t.Error("expected restart_required to be non-empty for ergo changes")
763c873… lmata 229 }
763c873… lmata 230
763c873… lmata 231 got := store.Get()
763c873… lmata 232 if got.Ergo.NetworkName != "testnet" {
763c873… lmata 233 t.Errorf("ergo.network_name = %q, want testnet", got.Ergo.NetworkName)
763c873… lmata 234 }
763c873… lmata 235 if got.Ergo.ServerName != "irc.test.local" {
763c873… lmata 236 t.Errorf("ergo.server_name = %q, want irc.test.local", got.Ergo.ServerName)
763c873… lmata 237 }
763c873… lmata 238 }
763c873… lmata 239
763c873… lmata 240 func TestHandlePutConfigTLS(t *testing.T) {
763c873… lmata 241 srv, store := newCfgTestServer(t)
763c873… lmata 242
763c873… lmata 243 update := map[string]any{
763c873… lmata 244 "tls": map[string]any{
763c873… lmata 245 "domain": "example.com",
763c873… lmata 246 "email": "[email protected]",
763c873… lmata 247 "allow_insecure": true,
763c873… lmata 248 },
763c873… lmata 249 }
763c873… lmata 250 body, _ := json.Marshal(update)
763c873… lmata 251 req, _ := http.NewRequest(http.MethodPut, srv.URL+"/v1/config", bytes.NewReader(body))
763c873… lmata 252 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 253 req.Header.Set("Content-Type", "application/json")
763c873… lmata 254 resp, err := http.DefaultClient.Do(req)
763c873… lmata 255 if err != nil {
763c873… lmata 256 t.Fatal(err)
763c873… lmata 257 }
763c873… lmata 258 defer resp.Body.Close()
763c873… lmata 259 if resp.StatusCode != http.StatusOK {
763c873… lmata 260 t.Fatalf("want 200, got %d", resp.StatusCode)
763c873… lmata 261 }
763c873… lmata 262
763c873… lmata 263 var result struct {
763c873… lmata 264 RestartRequired []string `json:"restart_required"`
763c873… lmata 265 }
763c873… lmata 266 json.NewDecoder(resp.Body).Decode(&result)
763c873… lmata 267 if len(result.RestartRequired) == 0 {
763c873… lmata 268 t.Error("expected restart_required for tls.domain change")
763c873… lmata 269 }
763c873… lmata 270
763c873… lmata 271 got := store.Get()
763c873… lmata 272 if got.TLS.Domain != "example.com" {
763c873… lmata 273 t.Errorf("tls.domain = %q, want example.com", got.TLS.Domain)
763c873… lmata 274 }
763c873… lmata 275 if got.TLS.Email != "[email protected]" {
763c873… lmata 276 t.Errorf("tls.email = %q, want [email protected]", got.TLS.Email)
763c873… lmata 277 }
763c873… lmata 278 if !got.TLS.AllowInsecure {
763c873… lmata 279 t.Error("tls.allow_insecure should be true")
763c873… lmata 280 }
763c873… lmata 281 }
763c873… lmata 282
763c873… lmata 283 func TestHandleGetConfigIncludesAgentPolicyAndLogging(t *testing.T) {
763c873… lmata 284 srv, store := newCfgTestServer(t)
763c873… lmata 285
763c873… lmata 286 cfg := store.Get()
763c873… lmata 287 cfg.AgentPolicy.RequireCheckin = true
763c873… lmata 288 cfg.AgentPolicy.CheckinChannel = "#ops"
763c873… lmata 289 cfg.Logging.Enabled = true
763c873… lmata 290 cfg.Logging.Format = "csv"
763c873… lmata 291 if err := store.Save(cfg); err != nil {
763c873… lmata 292 t.Fatalf("store.Save: %v", err)
763c873… lmata 293 }
763c873… lmata 294
763c873… lmata 295 req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/config", nil)
763c873… lmata 296 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 297 resp, err := http.DefaultClient.Do(req)
763c873… lmata 298 if err != nil {
763c873… lmata 299 t.Fatal(err)
763c873… lmata 300 }
763c873… lmata 301 defer resp.Body.Close()
763c873… lmata 302 if resp.StatusCode != http.StatusOK {
763c873… lmata 303 t.Fatalf("want 200, got %d", resp.StatusCode)
763c873… lmata 304 }
763c873… lmata 305
763c873… lmata 306 var body map[string]any
763c873… lmata 307 if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
763c873… lmata 308 t.Fatal(err)
763c873… lmata 309 }
763c873… lmata 310 ap, ok := body["agent_policy"].(map[string]any)
763c873… lmata 311 if !ok {
763c873… lmata 312 t.Fatal("response missing agent_policy section")
763c873… lmata 313 }
763c873… lmata 314 if ap["require_checkin"] != true {
763c873… lmata 315 t.Error("agent_policy.require_checkin should be true")
763c873… lmata 316 }
763c873… lmata 317 if ap["checkin_channel"] != "#ops" {
763c873… lmata 318 t.Errorf("agent_policy.checkin_channel = %v, want #ops", ap["checkin_channel"])
763c873… lmata 319 }
763c873… lmata 320 lg, ok := body["logging"].(map[string]any)
763c873… lmata 321 if !ok {
763c873… lmata 322 t.Fatal("response missing logging section")
763c873… lmata 323 }
763c873… lmata 324 if lg["enabled"] != true {
763c873… lmata 325 t.Error("logging.enabled should be true")
763c873… lmata 326 }
763c873… lmata 327 if lg["format"] != "csv" {
763c873… lmata 328 t.Errorf("logging.format = %v, want csv", lg["format"])
763c873… lmata 329 }
763c873… lmata 330 }
763c873… lmata 331
763c873… lmata 332 func TestHandleGetConfigHistoryEntry(t *testing.T) {
763c873… lmata 333 srv, store := newCfgTestServer(t)
763c873… lmata 334
763c873… lmata 335 // Save twice so a snapshot exists.
763c873… lmata 336 cfg := store.Get()
763c873… lmata 337 cfg.Bridge.WebUserTTLMinutes = 11
763c873… lmata 338 if err := store.Save(cfg); err != nil {
763c873… lmata 339 t.Fatalf("first save: %v", err)
763c873… lmata 340 }
763c873… lmata 341 cfg2 := store.Get()
763c873… lmata 342 cfg2.Bridge.WebUserTTLMinutes = 22
763c873… lmata 343 if err := store.Save(cfg2); err != nil {
763c873… lmata 344 t.Fatalf("second save: %v", err)
763c873… lmata 345 }
763c873… lmata 346
763c873… lmata 347 // List history to find a real filename.
763c873… lmata 348 entries, err := store.ListHistory()
763c873… lmata 349 if err != nil {
763c873… lmata 350 t.Fatalf("ListHistory: %v", err)
763c873… lmata 351 }
763c873… lmata 352 if len(entries) == 0 {
763c873… lmata 353 t.Skip("no history entries; snapshot may not have been created")
763c873… lmata 354 }
763c873… lmata 355 filename := entries[0].Filename
763c873… lmata 356
763c873… lmata 357 req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/config/history/"+filename, nil)
763c873… lmata 358 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 359 resp, err := http.DefaultClient.Do(req)
763c873… lmata 360 if err != nil {
763c873… lmata 361 t.Fatal(err)
763c873… lmata 362 }
763c873… lmata 363 defer resp.Body.Close()
763c873… lmata 364
763c873… lmata 365 if resp.StatusCode != http.StatusOK {
763c873… lmata 366 t.Fatalf("want 200, got %d", resp.StatusCode)
763c873… lmata 367 }
763c873… lmata 368 var body map[string]any
763c873… lmata 369 if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
763c873… lmata 370 t.Fatal(err)
763c873… lmata 371 }
763c873… lmata 372 if _, ok := body["bridge"]; !ok {
763c873… lmata 373 t.Error("history entry response missing bridge section")
763c873… lmata 374 }
763c873… lmata 375 }
763c873… lmata 376
763c873… lmata 377 func TestHandleGetConfigHistoryEntryNotFound(t *testing.T) {
763c873… lmata 378 srv, _ := newCfgTestServer(t)
763c873… lmata 379
763c873… lmata 380 req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/config/history/nonexistent.yaml", nil)
763c873… lmata 381 req.Header.Set("Authorization", "Bearer tok")
763c873… lmata 382 resp, err := http.DefaultClient.Do(req)
763c873… lmata 383 if err != nil {
763c873… lmata 384 t.Fatal(err)
763c873… lmata 385 }
763c873… lmata 386 defer resp.Body.Close()
763c873… lmata 387
763c873… lmata 388 if resp.StatusCode != http.StatusNotFound {
763c873… lmata 389 t.Fatalf("want 404, got %d", resp.StatusCode)
763c873… lmata 390 }
763c873… lmata 391 }
763c873… lmata 392
763c873… lmata 393 func TestConfigStoreOnChange(t *testing.T) {
763c873… lmata 394 dir := t.TempDir()
763c873… lmata 395 path := filepath.Join(dir, "scuttlebot.yaml")
763c873… lmata 396
763c873… lmata 397 var cfg config.Config
763c873… lmata 398 cfg.Defaults()
763c873… lmata 399 cfg.Ergo.DataDir = dir
763c873… lmata 400 store := NewConfigStore(path, cfg)
763c873… lmata 401
763c873… lmata 402 done := make(chan config.Config, 1)
763c873… lmata 403 store.OnChange(func(c config.Config) { done <- c })
763c873… lmata 404
763c873… lmata 405 next := store.Get()
763c873… lmata 406 next.Bridge.WebUserTTLMinutes = 99
763c873… lmata 407 if err := store.Save(next); err != nil {
763c873… lmata 408 t.Fatalf("Save: %v", err)
763c873… lmata 409 }
763c873… lmata 410
763c873… lmata 411 select {
763c873… lmata 412 case c := <-done:
763c873… lmata 413 if c.Bridge.WebUserTTLMinutes != 99 {
763c873… lmata 414 t.Errorf("OnChange got TTL=%d, want 99", c.Bridge.WebUserTTLMinutes)
763c873… lmata 415 }
763c873… lmata 416 case <-time.After(2 * time.Second):
763c873… lmata 417 t.Error("OnChange callback not called within timeout")
17e2c1d… lmata 418 }
17e2c1d… lmata 419 }
17e2c1d… lmata 420
17e2c1d… lmata 421 func TestHandleGetConfigHistory(t *testing.T) {
17e2c1d… lmata 422 srv, store := newCfgTestServer(t)
17e2c1d… lmata 423
17e2c1d… lmata 424 // Trigger a save to create a snapshot.
17e2c1d… lmata 425 cfg := store.Get()
17e2c1d… lmata 426 cfg.Bridge.WebUserTTLMinutes = 7
17e2c1d… lmata 427 if err := store.Save(cfg); err != nil {
17e2c1d… lmata 428 t.Fatalf("store.Save: %v", err)
17e2c1d… lmata 429 }
17e2c1d… lmata 430
17e2c1d… lmata 431 req, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/config/history", nil)
17e2c1d… lmata 432 req.Header.Set("Authorization", "Bearer tok")
17e2c1d… lmata 433 resp, err := http.DefaultClient.Do(req)
17e2c1d… lmata 434 if err != nil {
17e2c1d… lmata 435 t.Fatal(err)
17e2c1d… lmata 436 }
17e2c1d… lmata 437 defer resp.Body.Close()
17e2c1d… lmata 438
17e2c1d… lmata 439 if resp.StatusCode != http.StatusOK {
17e2c1d… lmata 440 t.Fatalf("want 200, got %d", resp.StatusCode)
17e2c1d… lmata 441 }
17e2c1d… lmata 442 var result struct {
17e2c1d… lmata 443 Entries []config.HistoryEntry `json:"entries"`
17e2c1d… lmata 444 }
17e2c1d… lmata 445 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
17e2c1d… lmata 446 t.Fatal(err)
17e2c1d… lmata 447 }
17e2c1d… lmata 448 // Save creates a snapshot of the *current* file before writing, but the
17e2c1d… lmata 449 // config file didn't exist yet, so no snapshot is created. Second save creates one.
17e2c1d… lmata 450 cfg2 := store.Get()
17e2c1d… lmata 451 cfg2.Bridge.WebUserTTLMinutes = 9
17e2c1d… lmata 452 if err := store.Save(cfg2); err != nil {
17e2c1d… lmata 453 t.Fatalf("store.Save 2: %v", err)
17e2c1d… lmata 454 }
17e2c1d… lmata 455
17e2c1d… lmata 456 req2, _ := http.NewRequest(http.MethodGet, srv.URL+"/v1/config/history", nil)
17e2c1d… lmata 457 req2.Header.Set("Authorization", "Bearer tok")
17e2c1d… lmata 458 resp2, err := http.DefaultClient.Do(req2)
17e2c1d… lmata 459 if err != nil {
17e2c1d… lmata 460 t.Fatal(err)
17e2c1d… lmata 461 }
17e2c1d… lmata 462 defer resp2.Body.Close()
17e2c1d… lmata 463
17e2c1d… lmata 464 var result2 struct {
17e2c1d… lmata 465 Entries []config.HistoryEntry `json:"entries"`
17e2c1d… lmata 466 }
17e2c1d… lmata 467 if err := json.NewDecoder(resp2.Body).Decode(&result2); err != nil {
17e2c1d… lmata 468 t.Fatal(err)
17e2c1d… lmata 469 }
17e2c1d… lmata 470 if len(result2.Entries) < 1 {
17e2c1d… lmata 471 t.Errorf("want ≥1 history entries, got %d", len(result2.Entries))
17e2c1d… lmata 472 }
17e2c1d… lmata 473 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button