test and lint corrections

This commit is contained in:
d1nch8g
2026-04-27 19:59:54 +03:00
parent f5fbea6cb9
commit ce9369c831
7 changed files with 90 additions and 91 deletions
+2 -2
View File
@@ -156,11 +156,11 @@ linters:
cyclop:
# The maximal code complexity to report.
# Default: 10
max-complexity: 30
max-complexity: 16
# The maximal average package complexity.
# If it's higher than 0.0 (float) the check is enabled.
# Default: 0.0
package-average: 10.0
package-average: 16.0
depguard:
# Rules to apply.
+1 -1
View File
@@ -497,7 +497,7 @@ func (s SearchAction) Execute(ctx context.Context, rt *Runtime) error {
return nil
}
func Parse(raw string) ([]Action, error) { //nolint:funlen
func Parse(raw string) ([]Action, error) { //nolint:funlen,cyclop // parser func, ok to be long
start := strings.Index(raw, "[")
end := strings.LastIndex(raw, "]")
if start != -1 && end != -1 && end > start {
+48 -51
View File
@@ -10,26 +10,28 @@ import (
)
const (
stage1 = `STAGE 1 TRIGGER. Ask user to compliment someone they care about. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a weekly reminder.`
stage2 = `STAGE 2 TRIGGER. Ask if user drinks enough water. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a daily hydration reminder.`
stage3 = `STAGE 3 TRIGGER. Ask if user stretches regularly. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a daily stretch reminder.`
stage4 = `STAGE 4 TRIGGER. Ask about user's sleep habits. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a bedtime reminder.`
stage5 = `STAGE 5 TRIGGER. Ask if user reads books or mostly consumes short-form content. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a reading reminder.`
stage6 = `STAGE 6 TRIGGER. Ask about exercise/sports. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a workout reminder.`
stage7 = `STAGE 7 TRIGGER. Ask if user is satisfied with their job/career. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to help with goal planning reminders.`
stage8 = `STAGE 8 TRIGGER. Ask about user's personal goals. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set goal-tracking reminders.`
stage1 = `SYSTEM: STAGE 1 TRIGGER. Ask user to compliment someone they care about. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a weekly reminder.`
stage2 = `SYSTEM: STAGE 2 TRIGGER. Ask if user drinks enough water. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a daily hydration reminder.`
stage3 = `SYSTEM: STAGE 3 TRIGGER. Ask if user stretches regularly. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a daily stretch reminder.`
stage4 = `SYSTEM: STAGE 4 TRIGGER. Ask about user's sleep habits. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a bedtime reminder.`
stage5 = `SYSTEM: STAGE 5 TRIGGER. Ask if user reads books or mostly consumes short-form content. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a reading reminder.`
stage6 = `SYSTEM: STAGE 6 TRIGGER. Ask about exercise/sports. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set a workout reminder.`
stage7 = `SYSTEM: STAGE 7 TRIGGER. Ask if user is satisfied with their job/career. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to help with goal planning reminders.`
stage8 = `SYSTEM: STAGE 8 TRIGGER. Ask about user's personal goals. If now is not a good time, create a non-system notification for this evening or another convenient time. Offer to set goal-tracking reminders.`
complete = `SYSTEM: COMPLETED INTEGRATION`
)
func Collect(user *user.User, source, content string, params ...any) actions.Action {
const (
d24 = 24 * time.Hour
d48 = 48 * time.Hour
d60 = 60 * time.Hour
d84 = 84 * time.Hour
d108 = 108 * time.Hour
d120 = 120 * time.Hour
)
func Collect(user *user.User, _, content string, _ ...any) actions.Action {
if user.Timezone == "" {
return nil
}
@@ -44,66 +46,61 @@ func Collect(user *user.User, source, content string, params ...any) actions.Act
}
}
switch {
case strings.Contains(content, "STAGE 1 TRIGGER"):
if !strings.HasPrefix(content, "SYSTEM: STAGE") {
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(48*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage2, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d48), user.Timezone),
Content: stage1,
}
}
case strings.Contains(content, "STAGE 2 TRIGGER"):
switch content[14] {
case '1':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(24*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage3, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d48), user.Timezone),
Content: stage2,
}
case strings.Contains(content, "STAGE 3 TRIGGER"):
case '2':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(24*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage4, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d24), user.Timezone),
Content: stage3,
}
case strings.Contains(content, "STAGE 4 TRIGGER"):
case '3':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(60*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage5, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d24), user.Timezone),
Content: stage4,
}
case strings.Contains(content, "STAGE 5 TRIGGER"):
case '4':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(84*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage6, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d60), user.Timezone),
Content: stage5,
}
case strings.Contains(content, "STAGE 6 TRIGGER"):
case '5':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(108*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage7, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d84), user.Timezone),
Content: stage6,
}
case strings.Contains(content, "STAGE 7 TRIGGER"):
case '6':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(120*time.Hour), user.Timezone),
Content: strings.ReplaceAll(stage8, "\n", " "),
Time: timeconv.ToLocal(time.Now().Add(d108), user.Timezone),
Content: stage7,
}
case strings.Contains(content, "STAGE 8 TRIGGER"):
case '7':
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(d120), user.Timezone),
Content: stage8,
}
default:
return actions.AddFact{
Type: "add_fact",
Value: complete,
}
}
return actions.AddNotification{
Type: "add_notification",
Time: timeconv.ToLocal(time.Now().Add(time.Hour*48), user.Timezone),
Content: strings.ReplaceAll(stage1, "\n", " "),
}
}
+21 -35
View File
@@ -18,12 +18,6 @@ func newTestUser() *user.User {
ID: uuid.New(),
Timezone: "UTC",
},
Chats: []database.Chat{},
Facts: []database.Fact{},
Contacts: []database.Contact{},
IncomingNotifications: []database.Notification{},
OutgoingNotifications: []database.Notification{},
RecentActions: []database.Action{},
}
}
@@ -72,7 +66,7 @@ func TestCollect_DefaultFirstRun(t *testing.T) {
func TestCollect_Stage1ToStage2(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 1 TRIGGER processed")
result := Collect(u, "", "SYSTEM: STAGE 1 TRIGGER processed")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -82,7 +76,7 @@ func TestCollect_Stage1ToStage2(t *testing.T) {
func TestCollect_Stage2ToStage3(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 2 TRIGGER done")
result := Collect(u, "", "SYSTEM: STAGE 2 TRIGGER done")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -92,7 +86,7 @@ func TestCollect_Stage2ToStage3(t *testing.T) {
func TestCollect_Stage3ToStage4(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "handling STAGE 3 TRIGGER now")
result := Collect(u, "", "SYSTEM: STAGE 3 TRIGGER now")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -102,7 +96,7 @@ func TestCollect_Stage3ToStage4(t *testing.T) {
func TestCollect_Stage4ToStage5(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 4 TRIGGER completed")
result := Collect(u, "", "SYSTEM: STAGE 4 TRIGGER completed")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -112,7 +106,7 @@ func TestCollect_Stage4ToStage5(t *testing.T) {
func TestCollect_Stage5ToStage6(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 5 TRIGGER fired")
result := Collect(u, "", "SYSTEM: STAGE 5 TRIGGER fired")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -122,7 +116,7 @@ func TestCollect_Stage5ToStage6(t *testing.T) {
func TestCollect_Stage6ToStage7(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 6 TRIGGER sent")
result := Collect(u, "", "SYSTEM: STAGE 6 TRIGGER sent")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -132,7 +126,7 @@ func TestCollect_Stage6ToStage7(t *testing.T) {
func TestCollect_Stage7ToStage8(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 7 TRIGGER done")
result := Collect(u, "", "SYSTEM: STAGE 7 TRIGGER done")
require.NotNil(t, result)
notif, ok := result.(actions.AddNotification)
@@ -142,7 +136,7 @@ func TestCollect_Stage7ToStage8(t *testing.T) {
func TestCollect_Stage8Completes(t *testing.T) {
u := newTestUser()
result := Collect(u, "", "STAGE 8 TRIGGER final")
result := Collect(u, "", "SYSTEM: STAGE 8 TRIGGER final")
fact, ok := result.(actions.AddFact)
require.True(t, ok)
@@ -159,9 +153,9 @@ func TestCollect_TimeFormats(t *testing.T) {
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
stages := []string{
"STAGE 1 TRIGGER", "STAGE 2 TRIGGER", "STAGE 3 TRIGGER",
"STAGE 4 TRIGGER", "STAGE 5 TRIGGER", "STAGE 6 TRIGGER",
"STAGE 7 TRIGGER",
"SYSTEM: STAGE 1 TRIGGER", "SYSTEM: STAGE 2 TRIGGER", "SYSTEM: STAGE 3 TRIGGER",
"SYSTEM: STAGE 4 TRIGGER", "SYSTEM: STAGE 5 TRIGGER", "SYSTEM: STAGE 6 TRIGGER",
"SYSTEM: STAGE 7 TRIGGER",
}
for _, stage := range stages {
result := Collect(u, "", stage)
@@ -178,7 +172,7 @@ func TestCollect_ContentNotLost(t *testing.T) {
result := Collect(u, "", "hello")
notif := result.(actions.AddNotification)
assert.True(t, strings.Contains(notif.Content, "user to compliment someone they care about"))
assert.True(t, strings.Contains(notif.Content, "compliment someone they care about"))
assert.False(t, strings.Contains(notif.Content, "\n"))
}
@@ -197,16 +191,15 @@ func TestCollect_AllFieldsValid(t *testing.T) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
assert.Equal(t, "add_notification", notif.Type)
assert.Empty(t, notif.Target, "Target must be empty for self-notification")
assert.Empty(t, notif.Target)
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 1 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 1 to 2",
content: "STAGE 1 TRIGGER done",
content: "SYSTEM: STAGE 1 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -215,12 +208,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 2 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 2 to 3",
content: "STAGE 2 TRIGGER done",
content: "SYSTEM: STAGE 2 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -229,12 +221,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 3 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 3 to 4",
content: "STAGE 3 TRIGGER done",
content: "SYSTEM: STAGE 3 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -243,12 +234,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 4 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 4 to 5",
content: "STAGE 4 TRIGGER done",
content: "SYSTEM: STAGE 4 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -257,12 +247,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 5 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 5 to 6",
content: "STAGE 5 TRIGGER done",
content: "SYSTEM: STAGE 5 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -271,12 +260,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 6 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 6 to 7",
content: "STAGE 6 TRIGGER done",
content: "SYSTEM: STAGE 6 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -285,12 +273,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 7 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 7 to 8",
content: "STAGE 7 TRIGGER done",
content: "SYSTEM: STAGE 7 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
notif, ok := a.(actions.AddNotification)
require.True(t, ok)
@@ -299,12 +286,11 @@ func TestCollect_AllFieldsValid(t *testing.T) {
assert.NotEmpty(t, notif.Time)
assert.Regexp(t, `^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$`, notif.Time)
assert.Contains(t, notif.Content, "STAGE 8 TRIGGER")
assert.NotContains(t, notif.Content, "\n")
},
},
{
name: "stage 8 completes",
content: "STAGE 8 TRIGGER done",
content: "SYSTEM: STAGE 8 TRIGGER done",
check: func(t *testing.T, a actions.Action) {
fact, ok := a.(actions.AddFact)
require.True(t, ok)
+5
View File
@@ -25,6 +25,11 @@ func (e *Engine) defaultProcessMessage(ctx context.Context, msg chat.Message) {
return
}
if msg.Text == "" {
span.Info("skipping empty message")
return
}
u, err := user.FromMessage(ctx, e.Database, msg, e.ActionLimit)
if err != nil {
span.Error("failed to get user from message", err)
+11
View File
@@ -444,6 +444,17 @@ func TestDefaultProcessMessage(t *testing.T) {
e.defaultProcessMessage(t.Context(), msg)
})
t.Run("empty message skipped", func(t *testing.T) {
var buf bytes.Buffer
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo})))
e, _ := setupTestEngine(t)
msg := chat.Message{Chat: "telegram", ID: "123", Text: ""}
e.defaultProcessMessage(t.Context(), msg)
assert.Contains(t, buf.String(), "skipping empty message")
})
}
func TestDefaultProcessNotification(t *testing.T) {
+2 -2
View File
@@ -34,7 +34,7 @@ NEVER END YOUR MESSAGES WITH DOTS
BE SKEPTICAL AND DON'T TRUST EVERYTHING
SPEAK LIKE A REAL HUMAN FRIEND — NO ROBOTIC PHRASES, NO FORMAL STRUCTURES, NO AWKWARD QUESTIONS. IF USER WOULDN'T SAY IT TO A FRIEND, DON'T SAY IT
MATCH USER'S TONE, LANGUAGE, AND MESSAGE LENGTH. SHORT INPUT → SHORT REPLY. LONG INPUT → CAN BE LONGER
ALWAYS SEND EMOJI IN SEPARATE MESSAGES
ALWAYS SEND EMOJI IN SEPARATE MESSAGES, SINGLE EMOJI PER MESSAGE
NEVER APPLY WAIT BEFORE FIRST MESSAGE, ALWAYS AFTER NEXT
IF YOU CREATE NOTIFICAION WHICH SHOULD BE REPEATED, BE VERY VERBOSE DESCRIBING REPETITION RULES, SO YOU WOULD BE ABLE TO SET NEXT RECIVING JUST FIRST repeat_on
ALL TIMES IN ACTIONS SHOULD BE PROVIDED IN FOLLOWING FORMAT: "2006-01-02 15:04" (no timezone)
@@ -283,7 +283,7 @@ func buildUserContext(user *user.User) string {
}
}
var tz = user.Timezone
tz := user.Timezone
if user.Timezone == "" {
tz = "(CRITICAL! ASK USER ABOUT HIS LOCATION/TIMEZONE, REQUIRED FOR NORMAL WORK FOR NOTIFICAIONS, WHEN USING SET - USE LINUX COMPATIBLE COMMAND)"
}