Skip to content

Add API tests #290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 6, 2025
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
4 changes: 2 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ linters:
- ineffassign
- noctx
- typecheck
- tparallel
- gofumpt
- misspell
- nilerr
Expand All @@ -92,7 +91,8 @@ linters:
- staticcheck
- unused
# todo enable after golangci-lint update to 1.63
# - gosec
# - tparallel
# - gosec
# - prealloc
# - mnd
# - govet
Expand Down
15 changes: 11 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@ generate:
go generate ./...

test:
./bin/local-startup.sh;
go test -v -cover -p 1 ./... -count=1
go test -v -cover ./... -count=1

test-integration:
go test -v -cover -tags=integration ./internal/indexer/db/...
go test -v -cover -tags=integration ./internal/indexer/db/

lint:
golangci-lint run
Expand All @@ -73,4 +72,12 @@ format:
gofumpt -l -w .

build-swagger:
swag init --parseDependency --parseInternal -d cmd/staking-api-service,internal/shared/api,internal/shared/types,internal/v1/api/handlers,internal/v2/api/handlers
swag init --parseDependency --parseInternal -d cmd/staking-api-service,internal/shared/api,internal/shared/types,internal/v1/api/handlers,internal/v2/api/handlers

# Runs end-to-end tests for API service
test-e2e:
go test -v -tags=e2e -coverprofile=cover.out -coverpkg=./internal/... ./tests/api/...

# Opens a browser to check code coverage stats. Note that output of test-e2e (cover.out) is required
coverage:
go tool cover -html=cover.out
2 changes: 1 addition & 1 deletion cmd/staking-api-service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func main() {
log.Fatal().Err(err).Msg(fmt.Sprintf("error while loading finality providers file: %s", finalityProvidersPath))
}

err = dbmodel.Setup(ctx, cfg)
err = dbmodel.Setup(ctx, cfg.StakingDb, cfg.ExternalAPIs)
if err != nil {
log.Fatal().Err(err).Msg("error while setting up staking db model")
}
Expand Down
18 changes: 0 additions & 18 deletions internal/indexer/db/client/finality_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,6 @@ import (
"go.mongodb.org/mongo-driver/bson"
)

// GetFinalityProviderByPk retrieves a single finality provider by their primary key
func (indexerdbclient *IndexerDatabase) GetFinalityProviderByPk(
ctx context.Context,
fpPk string,
) (*indexerdbmodel.IndexerFinalityProviderDetails, error) {
client := indexerdbclient.Client.Database(
indexerdbclient.DbName,
).Collection(indexerdbmodel.FinalityProviderDetailsCollection)

var result indexerdbmodel.IndexerFinalityProviderDetails
err := client.FindOne(ctx, bson.M{"_id": fpPk}).Decode(&result)
if err != nil {
return nil, err
}

return &result, nil
}

// GetFinalityProviders retrieves finality providers filtered by state
func (indexerdbclient *IndexerDatabase) GetFinalityProviders(
ctx context.Context,
Expand Down
1 change: 0 additions & 1 deletion internal/indexer/db/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ type IndexerDBClient interface {
GetBtcCheckpointParams(ctx context.Context) ([]*indexertypes.BtcCheckpointParams, error)
// Finality Providers
GetFinalityProviders(ctx context.Context) ([]*indexerdbmodel.IndexerFinalityProviderDetails, error)
GetFinalityProviderByPk(ctx context.Context, fpPk string) (*indexerdbmodel.IndexerFinalityProviderDetails, error)
// Staker Delegations
GetDelegation(ctx context.Context, stakingTxHashHex string) (*indexerdbmodel.IndexerDelegationDetails, error)
GetDelegations(ctx context.Context, stakerPKHex string, stakerBabylonAddress *string, paginationToken string) (*db.DbResultMap[indexerdbmodel.IndexerDelegationDetails], error)
Expand Down
1 change: 0 additions & 1 deletion internal/indexer/db/client/setup_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//go:build integration

// todo comment
package indexerdbclient_test

import (
Expand Down
38 changes: 0 additions & 38 deletions internal/indexer/db/model/finality_providers.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
package indexerdbmodel

import (
"encoding/json"
"fmt"

dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model"
"github.com/babylonlabs-io/staking-api-service/internal/shared/types"
)

type FinalityProviderState string

const (
Expand Down Expand Up @@ -38,33 +30,3 @@ type IndexerFinalityProviderPagination struct {
BtcPk string `json:"btc_pk"`
Commission string `json:"commission"`
}

func BuildFinalityProviderPaginationToken(f IndexerFinalityProviderDetails) (string, error) {
page := &IndexerFinalityProviderPagination{
BtcPk: f.BtcPk,
Commission: f.Commission,
}
token, err := dbmodel.GetPaginationToken(page)
if err != nil {
return "", err
}

return token, nil
}

func DecodeFinalityProviderPaginationToken(token string) (*IndexerFinalityProviderPagination, error) {
var pagination IndexerFinalityProviderPagination
err := json.Unmarshal([]byte(token), &pagination)
return &pagination, err
}

func FromStringToFinalityProviderState(s string) (types.FinalityProviderQueryingState, error) {
switch s {
case "active":
return types.FinalityProviderStateActive, nil
case "standby":
return types.FinalityProviderStateStandby, nil
default:
return "", fmt.Errorf("invalid finality provider state: %s", s)
}
}
74 changes: 0 additions & 74 deletions internal/shared/api/handlers/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"

indexerdbmodel "github.com/babylonlabs-io/staking-api-service/internal/indexer/db/model"
"github.com/babylonlabs-io/staking-api-service/internal/shared/bbnclient"
"github.com/babylonlabs-io/staking-api-service/internal/shared/config"
"github.com/babylonlabs-io/staking-api-service/internal/shared/services/service"
Expand Down Expand Up @@ -201,29 +199,6 @@ func ParseBtcAddressesQuery(
return addresses, nil
}

// ParseStateFilterQuery parses the state filter query and returns the state enum
// If the state is not provided, it returns an empty string
func ParseStateFilterQuery(
r *http.Request, queryName string,
) ([]types.DelegationState, *types.Error) {
states := r.URL.Query()[queryName]
if len(states) == 0 {
return nil, nil
}

var stateEnums []types.DelegationState
for _, state := range states {
stateEnum, err := types.FromStringToDelegationState(state)
if err != nil {
return nil, types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, err.Error(),
)
}
stateEnums = append(stateEnums, stateEnum)
}
return stateEnums, nil
}

// ParseBooleanQuery parses the boolean query and returns the boolean value
// If the boolean is not provided, it returns false
// If the boolean is not valid, it returns an error
Expand All @@ -246,52 +221,3 @@ func ParseBooleanQuery(
}
return value == "true", nil
}

func ParseFPSearchQuery(r *http.Request, queryName string, isOptional bool) (string, *types.Error) {
// max length of a public key in hex and the max length of a finality provider moniker is 64
const maxSearchQueryLength = 64
str := r.URL.Query().Get(queryName)
if str == "" {
if isOptional {
return "", nil
}
return "", types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, queryName+" is required",
)
}

if len(str) < 1 || len(str) > maxSearchQueryLength {
return "", types.NewErrorWithMsg(
http.StatusBadRequest,
types.BadRequest,
fmt.Sprintf("search query must be between 1 and %d characters", maxSearchQueryLength),
)
}

// check if the search query contains only printable ASCII characters
if !regexp.MustCompile(`^[\x20-\x7E]+$`).MatchString(str) {
return "", types.NewErrorWithMsg(
http.StatusBadRequest,
types.BadRequest,
fmt.Sprintf("%s contains invalid characters", queryName),
)
}

return str, nil
}

func ParseFPStateQuery(r *http.Request, isOptional bool) (types.FinalityProviderQueryingState, *types.Error) {
state := r.URL.Query().Get("state")
if state == "" {
if isOptional {
return "", nil
}
}
stateEnum, err := indexerdbmodel.FromStringToFinalityProviderState(state)
if err != nil {
return "", types.NewErrorWithMsg(
http.StatusBadRequest, types.BadRequest, err.Error(),
)
}
return stateEnum, nil
}
24 changes: 21 additions & 3 deletions internal/shared/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"context"
"fmt"
"net"
"net/http"

"github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers"
Expand All @@ -18,6 +19,7 @@ type Server struct {
httpServer *http.Server
handlers *handlers.Handlers
cfg *config.Config
listener net.Listener
}

func New(
Expand All @@ -38,7 +40,6 @@ func New(
r.Use(middlewares.ContentLengthMiddleware(cfg))

srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
WriteTimeout: cfg.Server.WriteTimeout,
ReadTimeout: cfg.Server.ReadTimeout,
IdleTimeout: cfg.Server.IdleTimeout,
Expand All @@ -60,6 +61,23 @@ func New(
}

func (a *Server) Start() error {
log.Info().Msgf("Starting server on %s", a.httpServer.Addr)
return a.httpServer.ListenAndServe()
address := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port)

log.Info().Msgf("Starting server on %s", address)
var err error
a.listener, err = net.Listen("tcp", address)
if err != nil {
return err
}

return a.httpServer.Serve(a.listener)
}

func (a *Server) Stop() error {
log.Info().Msg("Stopping server")
return a.httpServer.Shutdown(context.TODO())
}

func (a *Server) Addr() string {
return a.listener.Addr().String()
}
14 changes: 7 additions & 7 deletions internal/shared/db/model/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ var collections = map[string][]index{
},
}

func Setup(ctx context.Context, cfg *config.Config) error {
func Setup(ctx context.Context, stakingDB *config.DbConfig, externalConfig *config.ExternalAPIsConfig) error {
credential := options.Credential{
Username: cfg.StakingDb.Username,
Password: cfg.StakingDb.Password,
Username: stakingDB.Username,
Password: stakingDB.Password,
}
clientOps := options.Client().ApplyURI(cfg.StakingDb.Address).SetAuth(credential)
clientOps := options.Client().ApplyURI(stakingDB.Address).SetAuth(credential)
client, err := mongo.Connect(ctx, clientOps)
if err != nil {
return err
Expand All @@ -87,7 +87,7 @@ func Setup(ctx context.Context, cfg *config.Config) error {
defer cancel()

// Access a database and create collections.
database := client.Database(cfg.StakingDb.DbName)
database := client.Database(stakingDB.DbName)

// Create collections.
for collection := range collections {
Expand All @@ -101,8 +101,8 @@ func Setup(ctx context.Context, cfg *config.Config) error {
}

// If external APIs are configured, create TTL index for BTC price collection
if cfg.ExternalAPIs != nil {
if err := createTTLIndexes(ctx, database, cfg.ExternalAPIs.CoinMarketCap.CacheTTL); err != nil {
if externalConfig != nil {
if err := createTTLIndexes(ctx, database, externalConfig.CoinMarketCap.CacheTTL); err != nil {
log.Error().Err(err).Msg("Failed to create TTL index for BTC price")
return err
}
Expand Down
1 change: 1 addition & 0 deletions internal/shared/services/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Services struct {
}

func New(cfg *config.Config, globalParams *types.GlobalParams, finalityProviders []types.FinalityProviderDetails, clients *clients.Clients, dbClients *dbclients.DbClients) (*Services, error) {
// todo remove errors in service constructors (they are always nil)
sharedService, err := service.New(cfg, globalParams, finalityProviders, clients, dbClients)
if err != nil {
return nil, err
Expand Down
30 changes: 0 additions & 30 deletions internal/shared/utils/utils.go

This file was deleted.

1 change: 0 additions & 1 deletion internal/v2/api/handlers/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type AddressScreeningResponse struct {
// @Param btc_address query string true "BTC address to check"
// @Success 200 {object} handler.PublicResponse[AddressScreeningResponse] "Risk of provided address"
// @Failure 400 {object} types.Error "Error: Bad Request"
// @Failure 404 {object} types.Error "Error: Not Found"
// @Failure 500 {object} types.Error "Error: Internal Server Error"
// @Router /address/screening [get]
func (h *V2Handler) AddressScreening(request *http.Request) (*handler.Result, *types.Error) {
Expand Down
1 change: 0 additions & 1 deletion internal/v2/api/handlers/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ func (h *V2Handler) GetDelegations(request *http.Request) (*handler.Result, *typ
if err != nil {
return nil, err
}

bbnAddress, err := handler.ParseBabylonAddressQuery(
request, "babylon_address", true,
)
Expand Down
1 change: 0 additions & 1 deletion internal/v2/api/handlers/finality_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
// @Produce json
// @Tags v2
// @Success 200 {object} handler.PublicResponse[[]v2service.FinalityProviderStatsPublic] "List of finality providers with its stats"
// @Failure 400 {object} types.Error "Invalid parameters or malformed request"
// @Failure 404 {object} types.Error "No finality providers found"
// @Failure 500 {object} types.Error "Internal server error occurred"
// @Router /v2/finality-providers [get]
Expand Down
1 change: 1 addition & 0 deletions internal/v2/service/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ func (s *V2Service) GetDelegations(
ctx, stakerPkHex, stakerBabylonAddress, paginationKey,
)
if err != nil {
// todo this statement is not reachable
if db.IsNotFoundError(err) {
log.Ctx(ctx).Warn().Err(err).Str("stakingTxHashHex", stakerPkHex).Msg("Staking delegations not found")
return nil, "", types.NewErrorWithMsg(http.StatusNotFound, types.NotFound, "staking delegation not found, please retry")
Expand Down
Loading
Loading