827 lines
25 KiB
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)
|
|
}
|
|
}
|