Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions apps/poweron/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package config

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLoadConfig_WithAPITokenEnvVar(t *testing.T) {
// Set test environment variables
testRegion := "us-east-1"
testAPIToken := "test-api-token-123"
testSSMParamCache := "/test/cache/param"
testDynamoDBTable := "test-subscriptions-table"

os.Setenv("AWS_DEFAULT_REGION", testRegion)
os.Setenv(EnvAPIToken, testAPIToken)
os.Setenv(EnvSSMParamCache, testSSMParamCache)
os.Setenv(EnvDynamoDBSubscriptionsTable, testDynamoDBTable)
os.Unsetenv(EnvSSMParamAPIToken) // Ensure SSM param is not set

ctx := context.Background()
cfg, err := LoadConfig(ctx)
require.NoError(t, err)
require.NotNil(t, cfg)

// Verify API token
assert.Equal(t, testAPIToken, cfg.TelegramAPIToken)

// Verify SSM param cache
assert.Equal(t, testSSMParamCache, cfg.SSMParamCache)

// Verify DynamoDB table
assert.Equal(t, testDynamoDBTable, cfg.DynamoDBSubscriptionsTable)

// Verify default cache TTL
assert.Equal(t, PowerScheduleCacheTTL, cfg.PowerScheduleCacheTTL)

// Verify AWS config is set
assert.Equal(t, testRegion, cfg.AWSConfig.Region)
}

func TestLoadConfig_WithEmptyEnvVars(t *testing.T) {
// Unset all environment variables
os.Unsetenv(EnvAPIToken)
os.Unsetenv(EnvSSMParamAPIToken)
os.Unsetenv(EnvSSMParamCache)
os.Unsetenv(EnvDynamoDBSubscriptionsTable)

ctx := context.Background()
cfg, err := LoadConfig(ctx)

// When API token is not set and SSM param is not set, it should fail
// because it will try to get from SSM but the parameter name is empty
assert.Error(t, err)
assert.Nil(t, cfg)
}

func TestConfigConstants(t *testing.T) {
tests := []struct {
name string
constant string
want string
}{
{
name: "EnvAPIToken",
constant: EnvAPIToken,
want: "TELEGRAM_APITOKEN",
},
{
name: "EnvSSMParamAPIToken",
constant: EnvSSMParamAPIToken,
want: "SSM_PARAM_TELEGRAM_APITOKEN",
},
{
name: "EnvSSMParamCache",
constant: EnvSSMParamCache,
want: "SSM_PARAM_CACHE",
},
{
name: "EnvDynamoDBSubscriptionsTable",
constant: EnvDynamoDBSubscriptionsTable,
want: "DYNAMODB_TABLE_SUBSCRIPTIONS",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.constant)
})
}
}
4 changes: 4 additions & 0 deletions apps/poweron/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5
github.com/aws/aws-sdk-go-v2/service/ssm v1.67.7
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/stretchr/testify v1.7.2
)

require (
Expand All @@ -27,4 +28,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions apps/poweron/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
50 changes: 45 additions & 5 deletions apps/poweron/operations/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import (
"github.com/rvolykh/telegram-bot/apps/poweron/subscriptions"
)

const (
tomorrow = "Завтра"
today = "Сьогодні"
updatedAt = "Інформація станом на"
)

func DeliverScheduleUpdates(ctx context.Context, cfg *config.Config) error {
poweron := scrap.NewPoweron(cfg)

Expand Down Expand Up @@ -43,11 +49,18 @@ func DeliverScheduleUpdates(ctx context.Context, cfg *config.Config) error {
log.Printf("Failed to get pinned message %d: %s", subscriber.ChatID, err)
}

// telegram trims last \n so should we before compare
if prev == message[:len(message)-1] {
// one to one match
if prev.Text == message {
log.Printf("No updates, skipping for chat %d", subscriber.ChatID)
continue
}
// updatedAt change only
if isMessagesEqual(prev.Text, message) {
if err := t.EditMessage(ctx, subscriber.ChatID, prev.ID, message); err != nil {
log.Printf("Failed to edit message %d in chat %d: %s", prev.ID, subscriber.ChatID, err)
}
continue
}

messageID, err := t.SendMessage(ctx, subscriber.ChatID, message)
if err != nil {
Expand All @@ -68,13 +81,15 @@ func DeliverScheduleUpdates(ctx context.Context, cfg *config.Config) error {
func prepareMessage(powerSchedule scrap.Schedule, groups []string) string {
var message strings.Builder

message.WriteString("Сьогодні:\n")
message.WriteString(today + ":\n")
filterPowerScheduleGroups(&message, powerSchedule.Today, groups)

message.WriteString("\nЗавтра:\n")
message.WriteString("\n" + tomorrow + ":\n")
filterPowerScheduleGroups(&message, powerSchedule.Tomorrow, groups)

return message.String()
// remove last new line as telegram will strip it anyway and it can create issue in cmp
result := message.String()
return result[:len(result)-1]
}

func filterPowerScheduleGroups(b *strings.Builder, schedule string, groups []string) {
Expand All @@ -95,3 +110,28 @@ func filterPowerScheduleGroups(b *strings.Builder, schedule string, groups []str
b.WriteString(line + "\n")
}
}

func isMessagesEqual(src, dst string) bool {
var (
srcLines = strings.Split(src, "\n")
dstLines = strings.Split(dst, "\n")
)

if len(srcLines) != len(dstLines) {
return false
}

for i := range srcLines {
if srcLines[i] == dstLines[i] {
continue
}

if strings.HasPrefix(srcLines[i], updatedAt) && strings.HasPrefix(dstLines[i], updatedAt) {
continue
}

return false
}

return true
}
54 changes: 54 additions & 0 deletions apps/poweron/operations/schedule_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package operations

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSchedule_isMessagesEqual(t *testing.T) {
table := []struct {
A string
B string
want bool
}{
{
A: "",
B: "",
want: true,
},
{
A: `Є відключення`,
B: `Є відключення`,
want: true,
},
{
A: `Інформація станом на 10:00`,
B: `Інформація станом на 11:00`,
want: true,
},
{
A: "Є відключення\nІнформація станом на 10:00",
B: "Є відключення\nІнформація станом на 11:00",
want: true,
},
{
A: `Є відключення`,
B: `Нема відключення`,
want: false,
},
{
A: `Є відключення`,
B: ``,
want: false,
},
}

for i, tt := range table {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
have := isMessagesEqual(tt.A, tt.B)
assert.Equal(t, tt.want, have)
})
}
}
18 changes: 18 additions & 0 deletions apps/poweron/reply/edit_message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package reply

import (
"context"
"fmt"

tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

func (t *Telegram) EditMessage(ctx context.Context, chatID int64, messageID int, text string) error {
msg := tgbotapi.NewEditMessageText(chatID, messageID, text)

_, err := t.bot.Send(msg)
if err != nil {
return fmt.Errorf("failed to edit message %d: %w", messageID, err)
}
return nil
}
11 changes: 7 additions & 4 deletions apps/poweron/reply/get_pinned_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

func (t *Telegram) GetPinnedMessage(ctx context.Context, chatID int64) (string, error) {
func (t *Telegram) GetPinnedMessage(ctx context.Context, chatID int64) (Message, error) {
input := tgbotapi.ChatInfoConfig{
ChatConfig: tgbotapi.ChatConfig{
ChatID: chatID,
Expand All @@ -16,12 +16,15 @@ func (t *Telegram) GetPinnedMessage(ctx context.Context, chatID int64) (string,

output, err := t.bot.GetChat(input)
if err != nil {
return "", fmt.Errorf("failed to get pinned message: %w", err)
return Message{}, fmt.Errorf("failed to get pinned message: %w", err)
}

if output.PinnedMessage == nil {
return "", nil
return Message{}, nil
}

return output.PinnedMessage.Text, nil
return Message{
ID: output.PinnedMessage.MessageID,
Text: output.PinnedMessage.Text,
}, nil
}
6 changes: 6 additions & 0 deletions apps/poweron/reply/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package reply

type Message struct {
ID int
Text string
}