Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion tools/tools.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build tools
// +build tools

package tools

Expand Down
2 changes: 1 addition & 1 deletion xcclookup/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# xcclookup

xcclookup is a utility to assemble Quicksilver x/partiticipationrewards MsgSubmitClaim transactions for a user, in response to a REST request.
xcclookup is a utility to assemble Quicksilver x/participationrewards MsgSubmitClaim transactions for a user, in response to a REST request.

## Build

Expand Down
218 changes: 218 additions & 0 deletions xcclookup/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Testing Framework for xcclookup Package

This document describes the testing framework implemented for the xcclookup package, including unit tests, integration tests, and testing utilities.

## Overview

The testing framework has been designed to provide comprehensive coverage of the xcclookup package functionality while maintaining good separation of concerns and testability.

## Architecture

### Refactoring Summary

The original code has been refactored to improve testability:

1. **Interface-based Design**: Created interfaces for external dependencies
2. **Service Layer**: Extracted business logic into service classes
3. **Dependency Injection**: Made components accept dependencies as parameters
4. **Mock Implementations**: Created mock objects for testing

### Key Components

#### Interfaces (`pkgs/types/interfaces.go`)

- `VersionServiceInterface`: For version operations
- `CacheManagerInterface`: For cache operations
- `ClaimsServiceInterface`: For claims operations
- `RPCClientInterface`: For RPC client operations
- `OutputFunction`: For output functions

#### Services (`pkgs/services/`)

- `VersionService`: Handles version-related operations
- `CacheService`: Handles cache-related operations
- `AssetsService`: Handles assets-related operations

#### Handlers (`pkgs/handlers/`)

- `VersionHandler`: HTTP handler for version requests
- `CacheHandler`: HTTP handler for cache requests
- `AssetsHandler`: HTTP handler for assets requests

#### Mocks (`pkgs/mocks/`)

- `MockVersionService`: Mock implementation of VersionServiceInterface
- `MockCacheManager`: Mock implementation of CacheManagerInterface
- `MockClaimsService`: Mock implementation of ClaimsServiceInterface

## Test Structure

### Unit Tests

#### Service Tests

- `services/version_service_test.go`: Tests for VersionService
- `services/cache_service_test.go`: Tests for CacheService
- `services/assets_service_test.go`: Tests for AssetsService

#### Handler Tests

- `handlers/version_handler_test.go`: Tests for VersionHandler
- `handlers/cache_handler_test.go`: Tests for CacheHandler
- `handlers/assets_handler_test.go`: Tests for AssetsHandler

#### Claims Tests

- `claims/liquid_test.go`: Tests for liquid claims functionality

### Integration Tests

- `integration/handlers_integration_test.go`: End-to-end tests for handlers

## Running Tests

### Using the Test Runner Script

```bash
./run_tests.sh
```

This script will:

1. Run all unit tests
2. Run integration tests
3. Generate coverage reports
4. Create an HTML coverage report

### Manual Test Execution

```bash
# Run all tests
go test -v ./pkgs/...

# Run specific package tests
go test -v ./pkgs/services/...
go test -v ./pkgs/handlers/...
go test -v ./pkgs/claims/...

# Run with coverage
go test -v -coverprofile=coverage.out ./pkgs/...
go tool cover -html=coverage.out -o coverage.html
```

## Test Patterns

### Mock Usage

```go
// Create mock
mockVersionService := &mocks.MockVersionService{
GetVersionFunc: func() ([]byte, error) {
return []byte(`{"version":"1.0.0"}`), nil
},
}

// Use mock in service
service := services.NewVersionService(mockVersionService)
```

### Table-Driven Tests

```go
tests := []struct {
name string
input string
expectedResult string
expectedError error
}{
// Test cases...
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
```

### HTTP Handler Testing

```go
req := httptest.NewRequest(http.MethodGet, "/version", nil)
w := httptest.NewRecorder()
handler.Handle(w, req)

assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
```

## Coverage Goals

The testing framework aims for:

- **Unit Test Coverage**: >90% for business logic
- **Integration Test Coverage**: All major workflows
- **Error Handling**: All error paths tested
- **Edge Cases**: Boundary conditions and edge cases

## Best Practices

### Writing Tests

1. **Arrange-Act-Assert**: Structure tests with clear sections
2. **Descriptive Names**: Use descriptive test and variable names
3. **Single Responsibility**: Each test should test one thing
4. **Mock External Dependencies**: Don't test external services in unit tests
5. **Test Error Conditions**: Always test error paths

### Test Data

1. **Use Constants**: Define test data as constants
2. **Minimal Test Data**: Use only necessary data for each test
3. **Realistic Data**: Use realistic but minimal test data

### Assertions

1. **Specific Assertions**: Use specific assertions rather than generic ones
2. **Meaningful Messages**: Provide meaningful assertion messages
3. **Multiple Assertions**: Test multiple aspects when appropriate

## Maintenance

### Adding New Tests

1. Follow the existing patterns
2. Use the appropriate mock interfaces
3. Add integration tests for new workflows
4. Update this documentation

### Updating Tests

1. Keep tests in sync with code changes
2. Update mocks when interfaces change
3. Maintain test coverage goals
4. Review and refactor tests regularly

## Troubleshooting

### Common Issues

1. **Import Errors**: Ensure all dependencies are properly imported
2. **Mock Issues**: Verify mock implementations match interfaces
3. **Test Failures**: Check that test data matches expected results
4. **Coverage Issues**: Add tests for uncovered code paths

### Debugging Tests

1. Use `go test -v` for verbose output
2. Add debug prints in test functions
3. Use `t.Log()` for test logging
4. Check test coverage reports for gaps

## Future Enhancements

1. **Performance Tests**: Add benchmarks for critical paths
2. **Property-Based Testing**: Use property-based testing for complex logic
3. **Contract Testing**: Add contract tests for external dependencies
4. **Test Data Factories**: Create factories for complex test data
5. **Parallel Testing**: Enable parallel test execution where appropriate
25 changes: 14 additions & 11 deletions xcclookup/config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
logging:
level: info
bind_port: 8090
timeout: 3
source_chain: quicksilver-2
source_lcd: https://quicksilver-2.lcd.quicksilver.zone
chains:
cosmoshub-4: https://cosmoshub-4.rpc.quicksilver.zone:443
stargaze-1: https://stargaze-1.rpc.quicksilver.zone:443
#osmosis-1: https://osmosis-1.rpc.quicksilver.zone:443
osmosis-1: https://rpc-osmosis-01.stakeflow.io:443
osmosis-1: https://osmosis-1.rpc.quicksilver.zone:443
regen-1: https://regen-1.rpc.quicksilver.zone:443
sommelier-3: https://sommelier-3.rpc.quicksilver.zone:443
juno-1: https://juno-1.rpc.quicksilver.zone:443
Expand All @@ -19,15 +20,17 @@ chains:
archway-1: https://archway-1.rpc.quicksilver.zone:443
omniflixhub-1: https://omniflixhub-1.rpc.quicksilver.zone:443
injective-1: https://injective-1.rpc.quicksilver.zone:443
#mocks:
# umee_params:
# - chainid: umee-1
# connections:
# - connectionid: connection-13
# chainid: umee-1
# lastepoch: 11654900
# prefix: umee
# osmosis_pools:

mocks:
# umee_params:
# - chainid: umee-1
# connections:
# - connectionid: connection-2
# chainid: osmosis-1
# lastepoch: 39990000
# prefix: osmo
# transferchannel: channel-2
# osmosis_pools:
# - poolid: 1059
# poolname: qOSMO/OSMO
# lastupdated: 2024-04-27T17:00:44.560214732Z
Expand Down
2 changes: 1 addition & 1 deletion xcclookup/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ require (
github.com/cosmos/cosmos-sdk v0.47.17
github.com/golangci/golangci-lint/v2 v2.2.1
github.com/gorilla/mux v1.8.1
github.com/ingenuity-build/multierror v0.1.0
github.com/quicksilver-zone/quicksilver v1.8.2-0.20250707101051-6cb838a1fa0e
go.uber.org/multierr v1.11.0
golang.org/x/tools v0.34.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/gofumpt v0.8.0
Expand Down
13 changes: 8 additions & 5 deletions xcclookup/pkgs/claims/liquid.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package claims
import (
"context"
"fmt"
"log"
"time"

"github.com/cosmos/cosmos-sdk/codec"
Expand All @@ -19,6 +18,7 @@ import (
icstypes "github.com/quicksilver-zone/quicksilver/x/interchainstaking/types"
prewards "github.com/quicksilver-zone/quicksilver/x/participationrewards/types"

"github.com/quicksilver-zone/quicksilver/xcclookup/pkgs/logger"
"github.com/quicksilver-zone/quicksilver/xcclookup/pkgs/types"
)

Expand Down Expand Up @@ -58,6 +58,8 @@ func LiquidClaim(
connection prewards.ConnectionProtocolData,
height int64,
) (map[string]prewards.MsgSubmitClaim, map[string]sdk.Coins, error) {
log := logger.FromContext(ctx)

chain := connection.ChainID
prefix := connection.Prefix

Expand All @@ -74,7 +76,7 @@ func LiquidClaim(

host, ok := cfg.Chains[chain]
if !ok {
log.Printf("unable to find endpoint for %s", chain)
log.Warn("Unable to find endpoint for chain", "chain", chain)
// explicitly don't return an error here, as we don't want to fail the entire claim process for a temporary issue.
return nil, nil, nil
}
Expand Down Expand Up @@ -116,11 +118,11 @@ func LiquidClaim(
// add GetFiltered to CacheManager, to allow filtered lookups on a single field == value
laCache, err := types.GetCache[prewards.LiquidAllowedDenomProtocolData](ctx, cacheMgr)
if err != nil {
return nil, nil, fmt.Errorf("%w [GetCache[prewards.LiquidAllowedDenomProtocolData]]", err)
return nil, nil, err
}
zoneCache, err := types.GetCache[icstypes.Zone](ctx, cacheMgr)
if err != nil {
return nil, nil, fmt.Errorf("%w [GetCache[icstypes.Zone]]", err)
return nil, nil, err
}
tokens := GetTokenMap(laCache, zoneCache, chain, "", ignores)

Expand Down Expand Up @@ -151,7 +153,7 @@ func LiquidClaim(
lookupKey,
rpcclient.ABCIQueryOptions{Height: abciquery.Response.Height, Prove: true},
)
fmt.Println("Querying for value (liquid tokens)", "chain", chain, "address", address, "denom", tuple.denom) // debug?
log.Debug("Querying for value (liquid tokens)", "chain", chain, "address", address, "denom", tuple.denom)
// 7:
if err != nil {
return nil, nil, fmt.Errorf("%w [ABCIQueryWithOptions/gamm_tokens]", err)
Expand Down Expand Up @@ -181,5 +183,6 @@ func LiquidClaim(
msg[tuple.chain] = chainMsg
}

log.Debug("Liquid claim processing completed", "address", address, "chain", chain, "balance_count", len(queryResponse.Balances))
return msg, assets, nil
}
Loading
Loading