Skip to content

Commit 42c6fc4

Browse files
AchoArnoldCopilot
andcommitted
test(entities): add regression tests for ResolveScheduledAt
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5351ef9 commit 42c6fc4

3 files changed

Lines changed: 101 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package entities
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestResolveScheduledAt_NilSchedule_ReturnsCurrentUTC(t *testing.T) {
11+
now := time.Now()
12+
var schedule *MessageSendSchedule
13+
result := schedule.ResolveScheduledAt(now)
14+
assert.Equal(t, now.UTC(), result)
15+
}
16+
17+
func TestResolveScheduledAt_InactiveSchedule_ReturnsCurrentUTC(t *testing.T) {
18+
now := time.Now()
19+
schedule := &MessageSendSchedule{IsActive: false}
20+
result := schedule.ResolveScheduledAt(now)
21+
assert.Equal(t, now.UTC(), result)
22+
}
23+
24+
func TestResolveScheduledAt_NoWindows_ReturnsCurrentUTC(t *testing.T) {
25+
now := time.Now()
26+
schedule := &MessageSendSchedule{
27+
IsActive: true,
28+
Timezone: "UTC",
29+
Windows: []MessageSendScheduleWindow{},
30+
}
31+
result := schedule.ResolveScheduledAt(now)
32+
assert.Equal(t, now.UTC(), result)
33+
}
34+
35+
func TestResolveScheduledAt_WithinWindow_ReturnsCurrentUTC(t *testing.T) {
36+
// Wednesday at 10:00 UTC, window is Wed 9:00-17:00 (540-1020 minutes)
37+
now := time.Date(2025, 1, 1, 10, 0, 0, 0, time.UTC) // Wednesday
38+
schedule := &MessageSendSchedule{
39+
IsActive: true,
40+
Timezone: "UTC",
41+
Windows: []MessageSendScheduleWindow{
42+
{DayOfWeek: int(now.Weekday()), StartMinute: 540, EndMinute: 1020},
43+
},
44+
}
45+
result := schedule.ResolveScheduledAt(now)
46+
assert.Equal(t, now.UTC(), result)
47+
}
48+
49+
func TestResolveScheduledAt_BeforeWindow_ReturnsWindowStart(t *testing.T) {
50+
// Wednesday at 7:00 UTC, window is Wed 9:00-17:00
51+
now := time.Date(2025, 1, 1, 7, 0, 0, 0, time.UTC) // Wednesday
52+
schedule := &MessageSendSchedule{
53+
IsActive: true,
54+
Timezone: "UTC",
55+
Windows: []MessageSendScheduleWindow{
56+
{DayOfWeek: int(now.Weekday()), StartMinute: 540, EndMinute: 1020},
57+
},
58+
}
59+
result := schedule.ResolveScheduledAt(now)
60+
expected := time.Date(2025, 1, 1, 9, 0, 0, 0, time.UTC)
61+
assert.Equal(t, expected, result)
62+
}

api/pkg/repositories/gorm_phone_notification_repository.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,38 @@ func (repository *gormPhoneNotificationRepository) insert(
203203

204204
return nil
205205
}
206+
207+
// ScheduleExact stores a phone notification with an exact ScheduledAt time.
208+
// It performs a dedupe check — if a pending notification for the same message already exists, it's a no-op.
209+
func (repository *gormPhoneNotificationRepository) ScheduleExact(
210+
ctx context.Context,
211+
notification *entities.PhoneNotification,
212+
) error {
213+
ctx, span := repository.tracer.Start(ctx)
214+
defer span.End()
215+
216+
// Dedupe: check if a pending notification for this message already exists
217+
var count int64
218+
if err := repository.db.WithContext(ctx).
219+
Model(&entities.PhoneNotification{}).
220+
Where("message_id = ? AND status = ?", notification.MessageID, entities.PhoneNotificationStatusPending).
221+
Count(&count).Error; err != nil {
222+
return repository.tracer.WrapErrorSpan(
223+
span,
224+
stacktrace.Propagate(err, "cannot check for existing notification for message [%s]", notification.MessageID),
225+
)
226+
}
227+
228+
if count > 0 {
229+
return nil
230+
}
231+
232+
if err := repository.db.WithContext(ctx).Create(notification).Error; err != nil {
233+
return repository.tracer.WrapErrorSpan(
234+
span,
235+
stacktrace.Propagate(err, "cannot create exact-time notification with id [%s]", notification.ID),
236+
)
237+
}
238+
239+
return nil
240+
}

api/pkg/repositories/phone_notification_repository.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ type PhoneNotificationRepository interface {
1313
// Schedule a new entities.PhoneNotification
1414
Schedule(ctx context.Context, messagesPerMinute uint, schedule *entities.MessageSendSchedule, notification *entities.PhoneNotification) error
1515

16+
// ScheduleExact stores a phone notification with a fixed ScheduledAt time,
17+
// bypassing rate-limit and schedule window logic.
18+
ScheduleExact(ctx context.Context, notification *entities.PhoneNotification) error
19+
1620
// UpdateStatus of a notification
1721
UpdateStatus(ctx context.Context, notificationID uuid.UUID, status entities.PhoneNotificationStatus) error
1822

0 commit comments

Comments
 (0)