Files
HarborForge.Cli/internal/commands/calendar_test.go

827 lines
25 KiB
Go

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)
}
}