Files

175 lines
4.6 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"text/template"
"time"
)
func captureLogs() (*slog.Logger, *bytes.Buffer) {
var buf bytes.Buffer
h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
return slog.New(h), &buf
}
func mustTemplate(t *testing.T, raw string) *template.Template {
t.Helper()
tpl, err := template.New("t").Parse(raw)
if err != nil {
t.Fatalf("template parse: %v", err)
}
return tpl
}
func TestFire_HappyPath(t *testing.T) {
var gotMethod, gotAuth, gotVersion, gotBeta, gotContentType string
var gotBody []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotAuth = r.Header.Get("Authorization")
gotVersion = r.Header.Get("anthropic-version")
gotBeta = r.Header.Get("anthropic-beta")
gotContentType = r.Header.Get("Content-Type")
gotBody, _ = io.ReadAll(r.Body)
_, _ = w.Write([]byte(`{"type":"routine_fire","claude_code_session_id":"sess_1","claude_code_session_url":"https://claude.ai/code/sess_1"}`))
}))
defer srv.Close()
logger, logbuf := captureLogs()
c := &FireClient{
URL: srv.URL,
Token: "tok",
HTTP: &http.Client{Timeout: 5 * time.Second},
Template: mustTemplate(t, "fire {{.Cron}}"),
TZ: time.UTC,
Log: logger,
}
if err := c.Fire(context.Background(), "0 9 * * *"); err != nil {
t.Fatalf("Fire returned error: %v", err)
}
if gotMethod != http.MethodPost {
t.Errorf("method = %q", gotMethod)
}
if gotAuth != "Bearer tok" {
t.Errorf("auth = %q", gotAuth)
}
if gotVersion != headerVersion {
t.Errorf("anthropic-version = %q", gotVersion)
}
if gotBeta != headerBeta {
t.Errorf("anthropic-beta = %q", gotBeta)
}
if gotContentType != "application/json" {
t.Errorf("content-type = %q", gotContentType)
}
var body fireRequest
if err := json.Unmarshal(gotBody, &body); err != nil {
t.Fatalf("body unmarshal: %v", err)
}
if body.Text != "fire 0 9 * * *" {
t.Errorf("body.text = %q", body.Text)
}
if !strings.Contains(logbuf.String(), "fire ok") {
t.Errorf("expected 'fire ok' log, got: %s", logbuf.String())
}
if !strings.Contains(logbuf.String(), "https://claude.ai/code/sess_1") {
t.Errorf("expected session url in log, got: %s", logbuf.String())
}
}
func TestFire_NonOK(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
}))
defer srv.Close()
logger, logbuf := captureLogs()
c := &FireClient{
URL: srv.URL,
Token: "tok",
HTTP: &http.Client{Timeout: 5 * time.Second},
Template: mustTemplate(t, "x"),
TZ: time.UTC,
Log: logger,
}
if err := c.Fire(context.Background(), "0 9 * * *"); err != nil {
t.Fatalf("Fire should swallow non-2xx, got error: %v", err)
}
out := logbuf.String()
if !strings.Contains(out, "non-2xx") {
t.Errorf("expected non-2xx log, got: %s", out)
}
if !strings.Contains(out, "401") {
t.Errorf("expected status 401 in log, got: %s", out)
}
}
func TestFire_NetworkError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
srv.Close() // close immediately so requests fail
logger, logbuf := captureLogs()
c := &FireClient{
URL: srv.URL,
Token: "tok",
HTTP: &http.Client{Timeout: 1 * time.Second},
Template: mustTemplate(t, "x"),
TZ: time.UTC,
Log: logger,
}
if err := c.Fire(context.Background(), "0 9 * * *"); err != nil {
t.Fatalf("Fire should swallow network error, got: %v", err)
}
if !strings.Contains(logbuf.String(), "fire request failed") {
t.Errorf("expected request-failed log, got: %s", logbuf.String())
}
}
func TestFire_TemplateUsesNowFunc(t *testing.T) {
var gotBody []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotBody, _ = io.ReadAll(r.Body)
_, _ = w.Write([]byte(`{}`))
}))
defer srv.Close()
fixed := time.Date(2026, 5, 8, 23, 30, 0, 0, time.UTC)
logger, _ := captureLogs()
c := &FireClient{
URL: srv.URL,
Token: "tok",
HTTP: &http.Client{Timeout: 5 * time.Second},
Template: mustTemplate(t, "{{.LocalTime}}"),
TZ: time.UTC,
Log: logger,
NowFunc: func() time.Time { return fixed },
}
if err := c.Fire(context.Background(), "0 9 * * *"); err != nil {
t.Fatalf("Fire: %v", err)
}
var body fireRequest
if err := json.Unmarshal(gotBody, &body); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if body.Text != "2026-05-08 23:30 UTC" {
t.Errorf("body.text = %q", body.Text)
}
}