mirror of
https://github.com/tiennm99/claude-code-routine-trigger.git
synced 2026-10-13 22:15:38 +00:00
test: add unit tests for config, fire client, scheduler
This commit is contained in:
1 parent
9c4d7a532f
commit
e18d271970
3 files changed
+493
No files matched your search
@@ -0,0 +1,174 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user