Files
thefeed/test/e2e/auto_update_e2e_test.go
T
2026-04-29 01:45:27 +03:30

175 lines
5.7 KiB
Go

package e2e_test
import (
"io"
"testing"
)
// createDefaultProfile spins up a dummy "active" profile so the auto-update
// endpoints have somewhere to write. Returns the resulting profile id.
func createDefaultProfile(t *testing.T, base string) string {
t.Helper()
body := `{"action":"create","profile":{"id":"","nickname":"AU","config":{"domain":"au.example","key":"k","resolvers":["127.0.0.1:9999"],"queryMode":"single","rateLimit":0}}}`
resp := postJSON(t, base+"/api/profiles", body)
resp.Body.Close()
m := decodeJSON(t, getJSON(t, base+"/api/profiles"))
profs, ok := m["profiles"].([]any)
if !ok || len(profs) == 0 {
t.Fatalf("profile not created, got %v", m["profiles"])
}
return profs[0].(map[string]any)["id"].(string)
}
func TestE2E_AutoUpdate_GetEmpty(t *testing.T) {
base, _ := startWebServer(t)
createDefaultProfile(t, base)
resp := getJSON(t, base+"/api/auto-update")
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
t.Fatalf("expected 200, got %d body=%s", resp.StatusCode, body)
}
m := decodeJSON(t, resp)
chans, _ := m["channels"].([]any)
if len(chans) != 0 {
t.Errorf("expected empty channels, got %v", chans)
}
if d, _ := m["defaultIntervalSeconds"].(float64); int(d) != 60 {
t.Errorf("defaultIntervalSeconds = %v, want 60", m["defaultIntervalSeconds"])
}
}
func TestE2E_AutoUpdate_ToggleAddsAndRemoves(t *testing.T) {
base, _ := startWebServer(t)
createDefaultProfile(t, base)
// First toggle: add.
resp := postJSON(t, base+"/api/auto-update/toggle", `{"channel":"thefeed1"}`)
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
t.Fatalf("toggle add: %d body=%s", resp.StatusCode, body)
}
m := decodeJSON(t, resp)
if m["enabled"] != true {
t.Errorf("first toggle should set enabled=true, got %v", m["enabled"])
}
chans := m["channels"].([]any)
if len(chans) != 1 || chans[0] != "thefeed1" {
t.Errorf("channels after add = %v, want [thefeed1]", chans)
}
// Second toggle: remove.
resp2 := postJSON(t, base+"/api/auto-update/toggle", `{"channel":"thefeed1"}`)
m2 := decodeJSON(t, resp2)
if m2["enabled"] != false {
t.Errorf("second toggle should set enabled=false, got %v", m2["enabled"])
}
chans2 := m2["channels"]
if list, ok := chans2.([]any); !ok || len(list) != 0 {
t.Errorf("channels after remove = %v, want []", chans2)
}
}
func TestE2E_AutoUpdate_TogglesAtSign(t *testing.T) {
base, _ := startWebServer(t)
createDefaultProfile(t, base)
// Add with "@chan" — server must store stripped form.
postJSON(t, base+"/api/auto-update/toggle", `{"channel":"@chan"}`).Body.Close()
m := decodeJSON(t, getJSON(t, base+"/api/auto-update"))
chans := m["channels"].([]any)
if len(chans) != 1 || chans[0] != "chan" {
t.Errorf("channels = %v, want [chan]", chans)
}
// Toggle with bare form should remove the same entry.
resp := postJSON(t, base+"/api/auto-update/toggle", `{"channel":"chan"}`)
m2 := decodeJSON(t, resp)
if m2["enabled"] != false {
t.Errorf("expected enabled=false, got %v", m2["enabled"])
}
}
func TestE2E_AutoUpdate_PostReplacesList(t *testing.T) {
base, _ := startWebServer(t)
createDefaultProfile(t, base)
// POST with normalisation cases: leading @, dupes, whitespace.
body := `{"channels":["@a","b","@a"," c ",""],"intervalSeconds":120}`
resp := postJSON(t, base+"/api/auto-update", body)
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
t.Fatalf("POST: %d body=%s", resp.StatusCode, body)
}
m := decodeJSON(t, resp)
chans := m["channels"].([]any)
want := []string{"a", "b", "c"}
if len(chans) != len(want) {
t.Fatalf("channels len = %d, want %d (%v)", len(chans), len(want), chans)
}
for i, w := range want {
if chans[i] != w {
t.Errorf("channels[%d] = %v, want %q", i, chans[i], w)
}
}
if iv, _ := m["intervalSeconds"].(float64); int(iv) != 120 {
t.Errorf("intervalSeconds = %v, want 120", m["intervalSeconds"])
}
}
func TestE2E_AutoUpdate_IntervalFloor(t *testing.T) {
base, _ := startWebServer(t)
createDefaultProfile(t, base)
// Anything <60s gets bumped to the 60s floor; 0 stays 0 (means
// "follow the server's nextFetch cadence with the built-in default").
resp := postJSON(t, base+"/api/auto-update", `{"channels":["x"],"intervalSeconds":5}`)
m := decodeJSON(t, resp)
if iv, _ := m["intervalSeconds"].(float64); int(iv) != 60 {
t.Errorf("intervalSeconds floor = %v, want 60", m["intervalSeconds"])
}
resp2 := postJSON(t, base+"/api/auto-update", `{"channels":["x"],"intervalSeconds":0}`)
m2 := decodeJSON(t, resp2)
if iv, _ := m2["intervalSeconds"].(float64); int(iv) != 0 {
t.Errorf("intervalSeconds zero = %v, want 0 (default)", m2["intervalSeconds"])
}
}
func TestE2E_AutoUpdate_NoActiveProfile(t *testing.T) {
base, _ := startWebServer(t)
// No profile created → no active profile → POST should fail with 400.
resp := postJSON(t, base+"/api/auto-update/toggle", `{"channel":"x"}`)
if resp.StatusCode != 400 {
t.Fatalf("toggle without profile: expected 400, got %d", resp.StatusCode)
}
resp.Body.Close()
// GET should succeed and return empty channels.
resp2 := getJSON(t, base+"/api/auto-update")
if resp2.StatusCode != 200 {
t.Fatalf("GET without profile: expected 200, got %d", resp2.StatusCode)
}
m := decodeJSON(t, resp2)
chans, _ := m["channels"].([]any)
if len(chans) != 0 {
t.Errorf("expected empty channels with no profile, got %v", chans)
}
}
func TestE2E_AutoUpdate_PersistsAcrossGets(t *testing.T) {
base, _ := startWebServer(t)
createDefaultProfile(t, base)
postJSON(t, base+"/api/auto-update", `{"channels":["alpha","beta"]}`).Body.Close()
// Fresh GET should return the same list.
m := decodeJSON(t, getJSON(t, base+"/api/auto-update"))
chans := m["channels"].([]any)
if len(chans) != 2 || chans[0] != "alpha" || chans[1] != "beta" {
t.Errorf("channels persisted = %v, want [alpha beta]", chans)
}
}