Skip to content

Commit f4212d0

Browse files
authored
Merge branch 'main' into wals-without-mock
2 parents 0f4a8c0 + 06beba2 commit f4212d0

File tree

20 files changed

+609
-106
lines changed

20 files changed

+609
-106
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,4 @@ jobs:
3636
run: go build -v ./...
3737

3838
- name: Run tests
39-
run: go test -v ./...
40-
41-
- name: Run linter (optional)
42-
run: |
43-
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
44-
golangci-lint run
45-
46-
# - name: Run tests
47-
# env:
48-
# DATABASE_URL: ${{ secrets.DATABASE_URL }}
49-
# SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
50-
# run: go test -v ./...
39+
run: go test -v ./...

README.md

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
23
# Custom Alerts for Wallet Activities
34

45

@@ -13,7 +14,7 @@ This project allows users to set up personalized alerts for wallet activities li
1314

1415
- Personalized alerts for various wallet activities
1516

16-
- Notifications via email, SMS, or push notifications
17+
- Notifications via email
1718

1819
- Efficient filtering and indexing of on-chain events
1920

@@ -62,6 +63,56 @@ Create a `config.yaml` file in the root directory with the following content:
6263

6364
```sh
6465
client, err := ethclient.Dial("wss://mainnet.infura.io/ws/v3/YOUR_INFURA_PROJECT_ID")
65-
2. **Run the application**:
66+
2. **Configure user preferences**
67+
68+
userPreference := &models.UserPreference{
69+
UserID: "user@example.com",
70+
WalletAddress: "0x...",
71+
MinEtherValue: "1000000000000000000", // 1 ETH
72+
TrackNFTs: true,
73+
EmailNotification: true
74+
})
75+
76+
3. **Run the application**:
77+
```sh
78+
go run main.go
79+
## Testing
80+
81+
1. **Run all tests**:
82+
6683
```sh
67-
go run main.go
84+
go test ./... -v
85+
2. **Run specific tests**:
86+
87+
```sh
88+
go test -v ./services/... // Test notification services
89+
go test -v ./repository/... // Test repositories
90+
## Key Components
91+
92+
**NFT Detection**:
93+
94+
Pre-configured list of popular NFT contract addresses
95+
Extensible for adding new collections
96+
Transaction Processing:
97+
98+
**Real-time block monitoring**:
99+
100+
Transaction filtering and categorization
101+
Event creation and storage
102+
103+
**Notification System**:
104+
105+
Email notifications via SendGrid
106+
User preference-based filtering
107+
Customizable notification templates
108+
109+
## Development
110+
Project Structure
111+
112+
├── config/ # Configuration management
113+
├── database/ # Database initialization and connection
114+
├── models/ # Data models
115+
├── nft/ # NFT detection logic
116+
├── repository/ # Data access layer
117+
├── services/ # Business logic and notifications
118+
└── mock/ # Test mocks

config/config.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package config
22

33
import (
4+
"os"
5+
46
"github.com/spf13/viper"
57
)
68

79
type Config struct {
8-
InfuraProjectID string `mapstructure:"infura.project_id"`
9-
DatabaseURL string `mapstructure:"database.url"`
10-
SendGridAPIKey string `mapstructure:"sendgrid.api_key"`
10+
DatabaseURL string
11+
SendGridAPIKey string
12+
InfuraProjectID string
1113
}
1214

1315
func LoadConfig() (*Config, error) {
16+
if os.Getenv("GO_ENV") == "test" {
17+
return &Config{
18+
DatabaseURL: "test_db_url",
19+
SendGridAPIKey: "test_api_key",
20+
InfuraProjectID: "test_infura_id",
21+
}, nil
22+
}
1423
viper.SetConfigName("config")
1524
viper.AddConfigPath(".")
1625
viper.SetConfigType("yaml")

database/_database_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package database
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestInitDB(t *testing.T) {
10+
SetupMockDB()
11+
defer ResetMockDB()
12+
13+
t.Run("Mock Mode", func(t *testing.T) {
14+
db, err := InitDB("any-dsn")
15+
assert.Nil(t, err)
16+
assert.Nil(t, db)
17+
})
18+
19+
t.Run("Empty DSN", func(t *testing.T) {
20+
IsMockMode = false
21+
db, err := InitDB("")
22+
assert.Error(t, err)
23+
assert.Nil(t, db)
24+
})
25+
}

go.mod

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ require (
77
github.com/sendgrid/sendgrid-go v3.16.0+incompatible
88
github.com/spf13/viper v1.19.0
99
github.com/stretchr/testify v1.10.0
10-
gorm.io/driver/postgres v1.5.10
1110
gorm.io/gorm v1.25.12
1211
)
1312

@@ -29,10 +28,6 @@ require (
2928
github.com/gorilla/websocket v1.4.2 // indirect
3029
github.com/hashicorp/hcl v1.0.0 // indirect
3130
github.com/holiman/uint256 v1.3.1 // indirect
32-
github.com/jackc/pgpassfile v1.0.0 // indirect
33-
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
34-
github.com/jackc/pgx/v5 v5.5.5 // indirect
35-
github.com/jackc/puddle/v2 v2.2.1 // indirect
3631
github.com/jinzhu/inflection v1.0.0 // indirect
3732
github.com/jinzhu/now v1.1.5 // indirect
3833
github.com/magiconair/properties v1.8.7 // indirect

go.sum

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,6 @@ github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs
8686
github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
8787
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
8888
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
89-
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
90-
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
91-
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
92-
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
93-
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
94-
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
95-
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
96-
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
9789
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
9890
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
9991
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -177,7 +169,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
177169
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
178170
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
179171
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
180-
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
181172
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
182173
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
183174
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -234,8 +225,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
234225
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
235226
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
236227
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
237-
gorm.io/driver/postgres v1.5.10 h1:7Lggqempgy496c0WfHXsYWxk3Th+ZcW66/21QhVFdeE=
238-
gorm.io/driver/postgres v1.5.10/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
239228
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
240229
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
241230
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=

integration_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"math/big"
6+
"testing"
7+
"time"
8+
9+
"github.com/Jetlum/WalletAlertService/mock"
10+
"github.com/Jetlum/WalletAlertService/models"
11+
"github.com/ethereum/go-ethereum/common"
12+
"github.com/ethereum/go-ethereum/core/types"
13+
"github.com/ethereum/go-ethereum/crypto"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestIntegration(t *testing.T) {
18+
if testing.Short() {
19+
t.Skip("Skipping integration test in short mode")
20+
}
21+
22+
// Setup test context with timeout
23+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
24+
defer cancel()
25+
26+
userPref := models.UserPreference{
27+
UserID: "test@example.com",
28+
WalletAddress: "0xto",
29+
MinEtherValue: "500000000000000000", // 0.5 ETH
30+
EmailNotification: true,
31+
}
32+
33+
mockUserPrefRepo := &mock.MockUserPreferenceRepository{
34+
GetMatchingPreferencesFunc: func(e *models.Event) ([]models.UserPreference, error) {
35+
return []models.UserPreference{userPref}, nil
36+
},
37+
}
38+
39+
// Create mock Ethereum client
40+
mockClient := mock.NewMockEthClient()
41+
42+
// Create and use fromAddress in assertions
43+
privateKey, err := crypto.GenerateKey()
44+
if err != nil {
45+
t.Fatalf("Failed to generate private key: %v", err)
46+
}
47+
fromAddress := crypto.PubkeyToAddress(privateKey.PublicKey)
48+
49+
mockEventRepo := &mock.MockEventRepository{
50+
CreateFunc: func(e *models.Event) error {
51+
assert.Equal(t, fromAddress.Hex(), e.FromAddress, "From address should match")
52+
return nil
53+
},
54+
}
55+
// Create mock transaction (unsigned)
56+
mockTx := types.NewTransaction(
57+
0,
58+
common.HexToAddress("0xto"),
59+
big.NewInt(2000000000000000000), // 2 ETH (above threshold)
60+
21000,
61+
big.NewInt(1),
62+
nil,
63+
)
64+
65+
// Sign the transaction
66+
chainID := big.NewInt(1) // Use the same chain ID in the mock client
67+
signer := types.LatestSignerForChainID(chainID)
68+
signedTx, err := types.SignTx(mockTx, signer, privateKey)
69+
if err != nil {
70+
t.Fatalf("Failed to sign transaction: %v", err)
71+
}
72+
73+
// Update NetworkIDFunc to return the correct chain ID
74+
mockClient.NetworkIDFunc = func(ctx context.Context) (*big.Int, error) {
75+
return chainID, nil
76+
}
77+
78+
// Fix block.WithBody call
79+
mockClient.BlockByHashFunc = func(ctx context.Context, hash common.Hash) (*types.Block, error) {
80+
header := &types.Header{
81+
Number: big.NewInt(1),
82+
ParentHash: common.HexToHash("0x123"),
83+
Time: uint64(time.Now().Unix()),
84+
}
85+
86+
block := types.NewBlockWithHeader(header)
87+
return block.WithBody(types.Body{
88+
Transactions: []*types.Transaction{signedTx},
89+
Uncles: []*types.Header{},
90+
}), nil
91+
}
92+
93+
// Use mock NFT detector
94+
mockNFTDetector := &mock.MockNFTDetector{
95+
IsNFTTransactionFunc: func(tx *types.Transaction) bool {
96+
return false
97+
},
98+
}
99+
100+
emailSent := false
101+
mockEmailNotification := &mock.MockEmailNotification{
102+
SendFunc: func(event *models.Event, userPref *models.UserPreference) error {
103+
emailSent = true
104+
// Add assertions if needed
105+
return nil
106+
},
107+
}
108+
109+
// Create mock header
110+
mockHeader := &types.Header{
111+
Number: big.NewInt(1),
112+
}
113+
114+
done := make(chan bool)
115+
go func() {
116+
processBlock(
117+
mockClient,
118+
mockHeader,
119+
mockNFTDetector,
120+
mockEmailNotification,
121+
mockEventRepo,
122+
mockUserPrefRepo,
123+
)
124+
done <- true
125+
}()
126+
127+
select {
128+
case <-ctx.Done():
129+
t.Fatal("Test timed out")
130+
case <-done:
131+
assert.True(t, emailSent, "Email notification should have been sent")
132+
}
133+
}

0 commit comments

Comments
 (0)