Ctrl K
ComunidadGuías…

internal/task/schedule_write_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package task

import (
	"errors"
	"testing"
	"time"
)

func scheduleCreateContextFixture(referenceTime time.Time) ScheduleCreateContext {
	return ScheduleCreateContext{
		CreatorPersonID: "person-이샘플",
		Delivery: ScheduleDeliveryBinding{
			Platform:       "buzz",
			ConversationID: "channel-1",
			ReplyTargetID:  "post-1",
		},
		CompanyTimeZone: "Asia/Seoul",
		ReferenceTime:   referenceTime,
	}
}

func TestBuildScheduleCreateRefusesAnIncompleteDeliveryBinding(t *testing.T) {
	referenceTime := time.Now().UTC()
	input := ScheduleCreateInput{TaskInstruction: "brief me", Kind: "once", RunAt: referenceTime.Add(time.Hour).Format(time.RFC3339)}
	for expectedError, createContext := range map[error]ScheduleCreateContext{
		ErrScheduleRequesterRequired:    {Delivery: ScheduleDeliveryBinding{Platform: "buzz", ConversationID: "channel-1", ReplyTargetID: "post-1"}, ReferenceTime: referenceTime},
		ErrScheduleConversationRequired: {CreatorPersonID: "person-이샘플", Delivery: ScheduleDeliveryBinding{ReplyTargetID: "post-1"}, ReferenceTime: referenceTime},
		ErrScheduleReplyTargetRequired:  {CreatorPersonID: "person-이샘플", Delivery: ScheduleDeliveryBinding{Platform: "buzz", ConversationID: "channel-1"}, ReferenceTime: referenceTime},
	} {
		if _, errorValue := BuildScheduleCreate(input, createContext); !errors.Is(errorValue, expectedError) {
			t.Fatalf("error = %v, want %v", errorValue, expectedError)
		}
	}
}

func TestAScheduleCarriesAnApprovalOnlyWhenItsCreationWasApproved(t *testing.T) {
	referenceTime := time.Date(2026, 11, 3, 9, 0, 0, 0, time.UTC)
	for _, isApproved := range []bool{true, false} {
		createContext := scheduleCreateContextFixture(referenceTime)
		createContext.IsApproved = isApproved
		schedule, errorValue := BuildScheduleCreate(ScheduleCreateInput{
			TaskInstruction: "send the weekly report to the team channel",
			Kind:            "cron",
			CronExpression:  "0 17 * * 5",
			RepeatPolicy:    "unbounded",
		}, createContext)
		if errorValue != nil {
			t.Fatal(errorValue)
		}
		if schedule.IsApproved() != isApproved || isApproved && !schedule.ApprovedAt.Equal(referenceTime) {
			t.Fatalf("a schedule created with approval %v carries %v", isApproved, schedule.ApprovedAt)
		}
	}
}

func TestBuildScheduleCreateRequiresARepeatPolicyBound(t *testing.T) {
	referenceTime := time.Now().UTC()
	createContext := scheduleCreateContextFixture(referenceTime)
	for expectedError, repeatPolicy := range map[error]string{
		ErrScheduleRepeatPolicyRequired: "",
		ErrScheduleFiniteBoundRequired:  "finite",
	} {
		_, errorValue := BuildScheduleCreate(ScheduleCreateInput{
			TaskInstruction: "brief me",
			Kind:            "interval",
			IntervalSecond:  3600,
			RepeatPolicy:    repeatPolicy,
		}, createContext)
		if !errors.Is(errorValue, expectedError) {
			t.Fatalf("repeatPolicy %q error = %v, want %v", repeatPolicy, errorValue, expectedError)
		}
	}
}

func TestInitializeScheduleCreateRefusesASchedulePastItsOnlyRun(t *testing.T) {
	referenceTime := time.Now().UTC()

	_, errorValue := InitializeScheduleCreate(ScheduleCreateInput{
		TaskInstruction: "brief me",
		Kind:            "once",
		RunAt:           referenceTime.Add(-time.Hour).Format(time.RFC3339),
	}, scheduleCreateContextFixture(referenceTime))

	if !errors.Is(errorValue, ErrScheduleNoFutureRun) {
		t.Fatalf("error = %v, want %v", errorValue, ErrScheduleNoFutureRun)
	}
}

func TestInitializeScheduleCreateFallsBackToTheDefaultProfileAndTaskInstruction(t *testing.T) {
	referenceTime := time.Now().UTC()

	schedule, errorValue := InitializeScheduleCreate(ScheduleCreateInput{
		TaskInstruction: "주간 보고를 정리한다",
		Kind:            "once",
		RunAt:           referenceTime.Add(time.Hour).Format(time.RFC3339),
	}, scheduleCreateContextFixture(referenceTime))

	if errorValue != nil {
		t.Fatal(errorValue)
	}
	if schedule.AgentProfileName != "default" || schedule.Name != "주간 보고를 정리한다" || schedule.TimeZone != "Asia/Seoul" {
		t.Fatalf("unexpected schedule: %+v", schedule)
	}
}

func TestApplyScheduleUpdateRefusesAnInvalidKindInsteadOfFallingBackToOnce(t *testing.T) {
	referenceTime := time.Now().UTC()
	nextRunAt := referenceTime.Add(time.Hour)
	kind := "weekly"

	_, errorValue := ApplyScheduleUpdate(Schedule{
		ScheduleID:     "schedule-1",
		Kind:           ScheduleKindInterval,
		IntervalSecond: 3600,
		NextRunAt:      &nextRunAt,
	}, ScheduleUpdateInput{Kind: &kind}, "Asia/Seoul", referenceTime)

	if !errors.Is(errorValue, ErrScheduleKindInvalid) {
		t.Fatalf("error = %v, want %v", errorValue, ErrScheduleKindInvalid)
	}
}

func TestApplyScheduleUpdateClearsTheCadenceFieldsTheNewKindCannotUse(t *testing.T) {
	referenceTime := time.Now().UTC()
	nextRunAt := referenceTime.Add(time.Hour)
	kind := string(ScheduleKindOnce)
	runAt := nextRunAt.Format(time.RFC3339)

	updatedSchedule, errorValue := ApplyScheduleUpdate(Schedule{
		ScheduleID:     "schedule-1",
		Kind:           ScheduleKindInterval,
		IntervalSecond: 3600,
		MaxRunCount:    5,
		NextRunAt:      &nextRunAt,
	}, ScheduleUpdateInput{Kind: &kind, RunAt: &runAt}, "Asia/Seoul", referenceTime)

	if errorValue != nil {
		t.Fatal(errorValue)
	}
	if updatedSchedule.IntervalSecond != 0 || updatedSchedule.MaxRunCount != 0 {
		t.Fatalf("unexpected cadence: %+v", updatedSchedule)
	}
}

func TestApplyScheduleUpdateRefusesABlankTaskInstruction(t *testing.T) {
	referenceTime := time.Now().UTC()
	nextRunAt := referenceTime.Add(time.Hour)
	taskInstruction := "   "

	_, errorValue := ApplyScheduleUpdate(Schedule{
		ScheduleID: "schedule-1",
		Kind:       ScheduleKindOnce,
		RunAt:      &nextRunAt,
		NextRunAt:  &nextRunAt,
	}, ScheduleUpdateInput{TaskInstruction: &taskInstruction}, "Asia/Seoul", referenceTime)

	if !errors.Is(errorValue, ErrScheduleTaskInstructionRequired) {
		t.Fatalf("error = %v, want %v", errorValue, ErrScheduleTaskInstructionRequired)
	}
}

func TestScheduleUpdateChangesNothingAnswersAnEmptyInput(t *testing.T) {
	description := "주간 보고"
	if !ScheduleUpdateChangesNothing(ScheduleUpdateInput{}) {
		t.Fatal("an empty update input should change nothing")
	}
	if ScheduleUpdateChangesNothing(ScheduleUpdateInput{Description: &description}) {
		t.Fatal("a description update should change something")
	}
}