|
1 | 1 | package solana |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
| 5 | + "errors" |
4 | 6 | "testing" |
5 | 7 |
|
6 | 8 | solana "github.com/gagliardetto/solana-go" |
7 | 9 | "github.com/gagliardetto/solana-go/rpc" |
8 | 10 | "github.com/stretchr/testify/require" |
9 | 11 |
|
| 12 | + "github.com/smartcontractkit/chainlink-common/pkg/logger" |
10 | 13 | commonsol "github.com/smartcontractkit/chainlink-common/pkg/types/chains/solana" |
| 14 | + "github.com/smartcontractkit/chainlink-common/pkg/types/query" |
| 15 | + |
| 16 | + logpollertypes "github.com/smartcontractkit/chainlink-solana/pkg/solana/logpoller/types" |
11 | 17 | ) |
12 | 18 |
|
13 | 19 | func Test_Converters(t *testing.T) { |
@@ -288,3 +294,108 @@ func sig(i byte) solana.Signature { |
288 | 294 | func csig(i byte) commonsol.Signature { return commonsol.Signature(sig(i)) } |
289 | 295 |
|
290 | 296 | func uint64Ptr(v uint64) *uint64 { return &v } |
| 297 | + |
| 298 | +// mockLogPoller is a minimal LogPoller implementation for testing readiness guards. |
| 299 | +type mockLogPoller struct { |
| 300 | + ready error |
| 301 | +} |
| 302 | + |
| 303 | +func (m *mockLogPoller) Start(context.Context) error { return nil } |
| 304 | +func (m *mockLogPoller) Ready() error { return m.ready } |
| 305 | +func (m *mockLogPoller) Close() error { return nil } |
| 306 | +func (m *mockLogPoller) HasFilter(context.Context, string) bool { return false } |
| 307 | +func (m *mockLogPoller) RegisterFilter(context.Context, logpollertypes.Filter) error { |
| 308 | + return nil |
| 309 | +} |
| 310 | +func (m *mockLogPoller) UnregisterFilter(context.Context, string) error { return nil } |
| 311 | +func (m *mockLogPoller) GetFilters(context.Context) (map[string]logpollertypes.Filter, error) { |
| 312 | + return nil, nil |
| 313 | +} |
| 314 | +func (m *mockLogPoller) GetLatestBlock(context.Context) (int64, error) { return 0, nil } |
| 315 | +func (m *mockLogPoller) FilteredLogs(context.Context, []query.Expression, query.LimitAndSort, string) ([]logpollertypes.Log, error) { |
| 316 | + return nil, nil |
| 317 | +} |
| 318 | +func (m *mockLogPoller) Replay(int64) {} |
| 319 | +func (m *mockLogPoller) CPIEventsEnabled() bool { return false } |
| 320 | + |
| 321 | +// stubChain is a minimal Chain stub that only provides a LogPoller. |
| 322 | +// It embeds the Chain interface so unimplemented methods panic if called. |
| 323 | +type stubChain struct { |
| 324 | + Chain |
| 325 | + lp LogPoller |
| 326 | +} |
| 327 | + |
| 328 | +func (s *stubChain) LogPoller() LogPoller { return s.lp } |
| 329 | + |
| 330 | +func newSolanaService(t *testing.T, lpReady error) *solanaService { |
| 331 | + t.Helper() |
| 332 | + return &solanaService{ |
| 333 | + chain: &stubChain{lp: &mockLogPoller{ready: lpReady}}, |
| 334 | + logger: logger.Test(t), |
| 335 | + } |
| 336 | +} |
| 337 | + |
| 338 | +func TestLogPollerReadinessGuard(t *testing.T) { |
| 339 | + ctx := context.Background() |
| 340 | + errNotStarted := errors.New("not started") |
| 341 | + |
| 342 | + t.Run("RegisterLogTracking returns error when LogPoller not started", func(t *testing.T) { |
| 343 | + ss := newSolanaService(t, errNotStarted) |
| 344 | + err := ss.RegisterLogTracking(ctx, commonsol.LPFilterQuery{Name: "test"}) |
| 345 | + require.ErrorIs(t, err, ErrLogPollerNotStarted) |
| 346 | + }) |
| 347 | + |
| 348 | + t.Run("RegisterLogTracking succeeds when LogPoller is ready", func(t *testing.T) { |
| 349 | + ss := newSolanaService(t, nil) |
| 350 | + err := ss.RegisterLogTracking(ctx, commonsol.LPFilterQuery{ |
| 351 | + Name: "test", |
| 352 | + ContractIdlJSON: []byte(`{}`), |
| 353 | + }) |
| 354 | + require.NoError(t, err) |
| 355 | + }) |
| 356 | + |
| 357 | + t.Run("UnregisterLogTracking returns error when LogPoller not started", func(t *testing.T) { |
| 358 | + ss := newSolanaService(t, errNotStarted) |
| 359 | + err := ss.UnregisterLogTracking(ctx, "test") |
| 360 | + require.ErrorIs(t, err, ErrLogPollerNotStarted) |
| 361 | + }) |
| 362 | + |
| 363 | + t.Run("UnregisterLogTracking succeeds when LogPoller is ready", func(t *testing.T) { |
| 364 | + ss := newSolanaService(t, nil) |
| 365 | + err := ss.UnregisterLogTracking(ctx, "test") |
| 366 | + require.NoError(t, err) |
| 367 | + }) |
| 368 | + |
| 369 | + t.Run("QueryTrackedLogs returns error when LogPoller not started", func(t *testing.T) { |
| 370 | + ss := newSolanaService(t, errNotStarted) |
| 371 | + _, err := ss.QueryTrackedLogs(ctx, nil, query.LimitAndSort{}) |
| 372 | + require.ErrorIs(t, err, ErrLogPollerNotStarted) |
| 373 | + }) |
| 374 | + |
| 375 | + t.Run("GetLatestLPBlock returns error when LogPoller not started", func(t *testing.T) { |
| 376 | + ss := newSolanaService(t, errNotStarted) |
| 377 | + _, err := ss.GetLatestLPBlock(ctx) |
| 378 | + require.ErrorIs(t, err, ErrLogPollerNotStarted) |
| 379 | + }) |
| 380 | + |
| 381 | + t.Run("GetLatestLPBlock succeeds when LogPoller is ready", func(t *testing.T) { |
| 382 | + ss := newSolanaService(t, nil) |
| 383 | + block, err := ss.GetLatestLPBlock(ctx) |
| 384 | + require.NoError(t, err) |
| 385 | + require.NotNil(t, block) |
| 386 | + require.Equal(t, uint64(0), block.Slot) |
| 387 | + }) |
| 388 | + |
| 389 | + t.Run("GetFiltersNames returns error when LogPoller not started", func(t *testing.T) { |
| 390 | + ss := newSolanaService(t, errNotStarted) |
| 391 | + _, err := ss.GetFiltersNames(ctx) |
| 392 | + require.ErrorIs(t, err, ErrLogPollerNotStarted) |
| 393 | + }) |
| 394 | + |
| 395 | + t.Run("GetFiltersNames succeeds when LogPoller is ready", func(t *testing.T) { |
| 396 | + ss := newSolanaService(t, nil) |
| 397 | + names, err := ss.GetFiltersNames(ctx) |
| 398 | + require.NoError(t, err) |
| 399 | + require.Empty(t, names) |
| 400 | + }) |
| 401 | +} |
0 commit comments