TEST-CLI-CAL-001 TEST-CLI-PR-001 add CLI integration tests
This commit is contained in:
826
internal/commands/calendar_test.go
Normal file
826
internal/commands/calendar_test.go
Normal file
@@ -0,0 +1,826 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func writeTestConfig(t *testing.T, dir, baseURL string) {
|
||||
config := map[string]string{
|
||||
"base-url": baseURL,
|
||||
}
|
||||
data, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal config: %v", err)
|
||||
}
|
||||
cfgPath := filepath.Join(dir, ".hf-config.json")
|
||||
if err := os.WriteFile(cfgPath, data, 0644); err != nil {
|
||||
t.Fatalf("failed to write test config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func buildCLI(t *testing.T, cliPath string) {
|
||||
srcDir := filepath.Join("..", "..")
|
||||
cmd := exec.Command("go", "build", "-o", cliPath, filepath.Join(srcDir, "cmd", "hf"))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Skipf("cannot build CLI: %v (out: %s)", err, string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func runCLI(t *testing.T, dir, cliPath string, args ...string) (string, error) {
|
||||
cmd := exec.Command(cliPath, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = append(os.Environ(), "HF_TEST_MODE=1")
|
||||
out, err := cmd.CombinedOutput()
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// --- Tests: argument parsing / usage errors ---
|
||||
|
||||
func TestCalendarSchedule_MissingArgs(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
// --token must come after subcommand: hf calendar schedule --token <tok>
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "schedule", "--token", "fake")
|
||||
if err == nil {
|
||||
t.Fatalf("expected non-zero exit for missing args; got out=%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "usage:") && !strings.Contains(out, "slot-type") {
|
||||
t.Errorf("expected usage message with slot-type; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarSchedule_UnknownFlag(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "schedule", "--token", "fake", "Work", "09:00", "30", "--bad-flag")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for unknown flag")
|
||||
}
|
||||
if !strings.Contains(out, "unknown flag") {
|
||||
t.Errorf("expected 'unknown flag' in output; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarShow_UnknownFlag(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"slots": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "show", "--token", "fake", "--bad-flag")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for unknown flag")
|
||||
}
|
||||
if !strings.Contains(out, "unknown flag") {
|
||||
t.Errorf("expected 'unknown flag'; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarEdit_MissingSlotID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "edit", "--token", "fake")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for missing slot-id")
|
||||
}
|
||||
if !strings.Contains(out, "usage:") {
|
||||
t.Errorf("expected usage message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarCancel_MissingSlotID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "cancel", "--token", "fake")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for missing slot-id")
|
||||
}
|
||||
if !strings.Contains(out, "usage:") {
|
||||
t.Errorf("expected usage message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanSchedule_MissingAt(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-schedule", "--token", "fake", "Work", "30")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for missing --at")
|
||||
}
|
||||
if !strings.Contains(out, "--at") {
|
||||
t.Errorf("expected --at error; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanEdit_NothingToEdit(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-edit", "--token", "fake", "1")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for nothing to edit")
|
||||
}
|
||||
if !strings.Contains(out, "nothing to edit") {
|
||||
t.Errorf("expected 'nothing to edit' error; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanCancel_MissingPlanID(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-cancel", "--token", "fake")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for missing plan-id")
|
||||
}
|
||||
if !strings.Contains(out, "usage:") {
|
||||
t.Errorf("expected usage message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests: JSON output ---
|
||||
|
||||
func TestCalendarSchedule_JSONOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/calendar/slots" {
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"slot_id": 42,
|
||||
"slot_type": "Work",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "--json", "calendar", "schedule", "--token", "fake", "Work", "09:00", "30")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(out), &resp); err != nil {
|
||||
t.Fatalf("output is not valid JSON: %s", out)
|
||||
}
|
||||
if resp["slot_id"] != float64(42) {
|
||||
t.Errorf("expected slot_id=42; got: %v", resp["slot_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarShow_JSONOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/calendar/day" {
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"slots": []interface{}{
|
||||
map[string]interface{}{
|
||||
"slot_id": 1,
|
||||
"slot_type": "Work",
|
||||
"scheduled_at": "09:00",
|
||||
"estimated_duration": 30,
|
||||
"priority": 50,
|
||||
"status": "NotStarted",
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "--json", "calendar", "show", "--token", "fake", "--date", "2026-04-01")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(out), &resp); err != nil {
|
||||
t.Fatalf("output is not valid JSON: %s", out)
|
||||
}
|
||||
slots, ok := resp["slots"].([]interface{})
|
||||
if !ok || len(slots) == 0 {
|
||||
t.Fatalf("expected slots array in JSON; got: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarDateList_JSONOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/calendar/dates" {
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"dates": []string{"2026-04-01", "2026-04-02"},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "--json", "calendar", "date-list", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(out), &resp); err != nil {
|
||||
t.Fatalf("output is not valid JSON: %s", out)
|
||||
}
|
||||
dates, ok := resp["dates"].([]interface{})
|
||||
if !ok || len(dates) != 2 {
|
||||
t.Errorf("expected 2 dates; got: %v", dates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanList_JSONOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/calendar/plans" {
|
||||
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"plans": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"slot_type": "Work",
|
||||
"at_time": "09:00",
|
||||
"estimated_duration": 30,
|
||||
"is_active": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "--json", "calendar", "plan-list", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
var resp map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(out), &resp); err != nil {
|
||||
t.Fatalf("output is not valid JSON: %s", out)
|
||||
}
|
||||
plans, ok := resp["plans"].([]interface{})
|
||||
if !ok || len(plans) == 0 {
|
||||
t.Fatalf("expected plans array; got: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests: human-readable output ---
|
||||
|
||||
func TestCalendarShow_HumanOutput_WithSlots(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"slots": []interface{}{
|
||||
map[string]interface{}{
|
||||
"slot_id": 1,
|
||||
"slot_type": "Work",
|
||||
"scheduled_at": "09:00",
|
||||
"estimated_duration": 30,
|
||||
"priority": 50,
|
||||
"status": "NotStarted",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"slot_id": "plan-1-2026-04-01",
|
||||
"slot_type": "OnCall",
|
||||
"scheduled_at": "14:00",
|
||||
"estimated_duration": 60,
|
||||
"priority": 40,
|
||||
"status": "NotStarted",
|
||||
"is_virtual": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "show", "--token", "fake", "--date", "2026-04-01")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
// Virtual slot should be marked as plan
|
||||
if !strings.Contains(out, "plan") {
|
||||
t.Errorf("expected human output to mark virtual slot as plan; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarShow_HumanOutput_Empty(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"slots": []interface{}{},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "show", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "No slots") {
|
||||
t.Errorf("expected 'No slots' for empty; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarDateList_HumanOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"dates": []string{"2026-04-01", "2026-04-02"},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "date-list", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
for _, date := range []string{"2026-04-01", "2026-04-02"} {
|
||||
if !strings.Contains(out, date) {
|
||||
t.Errorf("expected date %s in output; got: %s", date, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarDateList_HumanOutput_Empty(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"dates": []string{},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "date-list", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "No future dates") {
|
||||
t.Errorf("expected 'No future dates'; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanList_HumanOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"plans": []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": 1,
|
||||
"slot_type": "Work",
|
||||
"at_time": "09:00",
|
||||
"on_day": "Mon",
|
||||
"estimated_duration": 30,
|
||||
"is_active": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-list", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "09:00") || !strings.Contains(out, "Work") {
|
||||
t.Errorf("expected plan data in output; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanList_HumanOutput_Empty(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"plans": []interface{}{},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-list", "--token", "fake")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "No schedule plans") {
|
||||
t.Errorf("expected 'No schedule plans'; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests: error output ---
|
||||
|
||||
func TestCalendarShow_ServerError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(`{"detail":"internal error"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "show", "--token", "fake")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for 500 response")
|
||||
}
|
||||
if !strings.Contains(out, "failed") && !strings.Contains(out, "error") {
|
||||
t.Errorf("expected error message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarEdit_SlotNotFound(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(`{"detail":"slot not found"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "edit", "--token", "fake", "999", "--slot-type", "Work")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for 404")
|
||||
}
|
||||
if !strings.Contains(out, "failed") && !strings.Contains(out, "error") {
|
||||
t.Errorf("expected error message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarCancel_SlotNotFound(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(`{"detail":"slot not found"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "cancel", "--token", "fake", "999")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for 404")
|
||||
}
|
||||
if !strings.Contains(out, "failed") && !strings.Contains(out, "error") {
|
||||
t.Errorf("expected error message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanSchedule_ServerError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(`{"detail":"db error"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-schedule", "--token", "fake", "Work", "30", "--at", "09:00")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for 500")
|
||||
}
|
||||
if !strings.Contains(out, "failed") && !strings.Contains(out, "error") {
|
||||
t.Errorf("expected error message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanCancel_PlanNotFound(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte(`{"detail":"plan not found"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-cancel", "--token", "fake", "999")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for 404")
|
||||
}
|
||||
if !strings.Contains(out, "failed") && !strings.Contains(out, "error") {
|
||||
t.Errorf("expected error message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests: workload warnings ---
|
||||
|
||||
func TestCalendarSchedule_WorkloadWarning(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"slot_id": 1,
|
||||
"warnings": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "workload",
|
||||
"message": "Daily minimum work workload (30 min) not met: current 0 min",
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "schedule", "--token", "fake", "Work", "09:00", "30")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "⚠") && !strings.Contains(out, "warning") {
|
||||
t.Errorf("expected workload warning in output; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests: virtual slot routing ---
|
||||
|
||||
func TestCalendarEdit_VirtualSlot_RoutesCorrectly(t *testing.T) {
|
||||
var editedPath string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
editedPath = r.URL.Path
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"slot_id": 10,
|
||||
"slot_type": "Work",
|
||||
"scheduled_at": "10:00",
|
||||
"estimated_duration": 30,
|
||||
"status": "NotStarted",
|
||||
"priority": 50,
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "edit", "--token", "fake", "plan-1-2026-04-01", "--scheduled-at", "10:00")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(editedPath, "/calendar/slots/virtual/") {
|
||||
t.Errorf("expected virtual slot path /calendar/slots/virtual/...; got: %s", editedPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarCancel_VirtualSlot_RoutesCorrectly(t *testing.T) {
|
||||
var cancelledPath string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cancelledPath = r.URL.Path
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
_, err := runCLI(t, tmpDir, cliPath, "calendar", "cancel", "--token", "fake", "plan-1-2026-04-01")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(cancelledPath, "/calendar/slots/virtual/") {
|
||||
t.Errorf("expected virtual slot cancel path /calendar/slots/virtual/...; got: %s", cancelledPath)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests: successful operations ---
|
||||
|
||||
func TestCalendarSchedule_SuccessOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"slot_id": 5})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "schedule", "--token", "fake", "Work", "09:00", "30", "--job", "TASK-1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "slot scheduled") {
|
||||
t.Errorf("expected 'slot scheduled' success message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanSchedule_SuccessOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"id": 1})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-schedule", "--token", "fake", "Work", "30", "--at", "09:00", "--on-day", "Mon")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "plan created") {
|
||||
t.Errorf("expected 'plan created' success message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanEdit_SuccessOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"id": 1, "at_time": "10:00"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-edit", "--token", "fake", "1", "--at", "10:00")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "plan edited") {
|
||||
t.Errorf("expected 'plan edited' success message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarPlanCancel_SuccessOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "plan-cancel", "--token", "fake", "1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "plan cancelled") {
|
||||
t.Errorf("expected 'plan cancelled' success message; got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalendarCancel_SuccessOutput(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(`{}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
writeTestConfig(t, tmpDir, server.URL)
|
||||
cliPath := filepath.Join(tmpDir, "hf")
|
||||
buildCLI(t, cliPath)
|
||||
|
||||
out, err := runCLI(t, tmpDir, cliPath, "calendar", "cancel", "--token", "fake", "1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v; out=%s", err, out)
|
||||
}
|
||||
if !strings.Contains(out, "slot cancelled") {
|
||||
t.Errorf("expected 'slot cancelled' success message; got: %s", out)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user