Skip to content
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

Production Release 2025-02-24_01 #2766

Merged
merged 5 commits into from
Feb 24, 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
2 changes: 1 addition & 1 deletion .github/workflows/generalized-deployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ jobs:
run: echo "GITHUB_REF_OVERRIDE=refs/heads/dev" >> $GITHUB_ENV
if: ${{ github.ref == 'refs/heads/master' }}
- name: Generalized Deployments
uses: brave-intl/general-docker-build-pipeline-action@7eb4e5063b3051fb0eb49da64c5c623ef7813ef2 # v1.0.17
uses: brave-intl/general-docker-build-pipeline-action@539cd1f2cfb5d0df8ce9727842a9e9936e31c49d # v1.0.18
26 changes: 21 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ ifdef TEST_RUN
TEST_FLAGS = --tags=$(TEST_TAGS) $(TEST_PKG) --run=$(TEST_RUN)
endif

# Define default test directories if not specified
TEST_DIRS?= libs services tools cmd
# Allow specifying single directory via TEST_DIR=<dirname>
ifdef TEST_DIR
TEST_DIRS = $(TEST_DIR)
endif

.PHONY: all buildcmd docker test create-json-schema lint clean download-mod
all: test create-json-schema buildcmd

Expand Down Expand Up @@ -116,7 +123,14 @@ docker-test:
COMMIT=$(GIT_COMMIT) VERSION=$(GIT_VERSION) BUILD_TIME=$(BUILD_TIME) docker compose \
-f docker-compose.yml -f docker-compose.dev.yml up -d vault
$(eval VAULT_TOKEN = $(shell docker logs grant-vault 2>&1 | grep "Root Token" | tail -1 | cut -d ' ' -f 3 ))
VAULT_TOKEN=$(VAULT_TOKEN) PKG=$(TEST_PKG) RUN=$(TEST_RUN) docker compose -f docker-compose.yml -f docker-compose.dev.yml run --rm dev make test && cd main && go run main.go generate json-schema
VAULT_TOKEN=$(VAULT_TOKEN) TEST_DIRS="$(TEST_DIRS)" TEST_PKG=$(TEST_PKG) TEST_RUN=$(TEST_RUN) docker compose -f docker-compose.yml -f docker-compose.dev.yml run -T --rm dev make test

docker-test-redis:
COMMIT=$(GIT_COMMIT) VERSION=$(GIT_VERSION) BUILD_TIME=$(BUILD_TIME) docker compose \
--profile redis-log -f docker-compose.yml -f docker-compose.dev.yml up -d redis --wait
docker exec grant-redis redis-cli MONITOR &
-make docker-test
docker compose -f docker-compose.yml -f docker-compose.dev.yml stop

docker-dev:
$(eval VAULT_TOKEN = $(shell docker logs grant-vault 2>&1 | grep "Root Token" | tail -1 | cut -d ' ' -f 3 ))
Expand Down Expand Up @@ -170,10 +184,12 @@ create-json-schema:
cd main && go run main.go generate json-schema

test:
cd libs && go test -count 1 -v -p 1 $(TEST_FLAGS) ./...
cd services && go test -count 1 -v -p 1 $(TEST_FLAGS) ./...
cd tools && go test -count 1 -v -p 1 $(TEST_FLAGS) ./...
cd cmd && go test -count 1 -v -p 1 $(TEST_FLAGS) ./...
@for dir in $(TEST_DIRS); do \
if [ -d "$$dir" ]; then \
echo "Testing $$dir..."; \
cd $$dir && go test -count 1 -v -p 1 $(TEST_FLAGS) && cd .. || exit 1; \
fi \
done

format:
gofmt -s -w ./
Expand Down
14 changes: 14 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
version: "3.4"

volumes:
gomod:
out:
driver_opts:
type: tmpfs
Expand All @@ -11,6 +12,7 @@ services:
volumes:
- ".:/src"
- "out:/out"
- "gomod:/go/pkg/mod"
security_opt:
- no-new-privileges:true
environment:
Expand All @@ -21,8 +23,20 @@ services:
- VAULT_TOKEN
- TEST_RUN
- TEST_PKG
- TEST_DIRS
vault:
container_name: grant-vault
image: vault:0.10.2
networks:
- grant
redislog:
container_name: grant-redis-log
image: redis
networks:
- grant
command: ["redis-cli", "-h", "grant-redis", "MONITOR"]
profiles:
- redis-log
depends_on:
redis:
condition: service_healthy
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,10 @@ services:
- KAFKA_SSL_CERTIFICATE_LOCATION=/etc/kafka/secrets/consumer-ca1-signed.pem
- KAFKA_SSL_KEY_LOCATION=/etc/kafka/secrets/consumer.client.key
- OUTPUT_DIR="/out"
- PKG
- PPROF_ENABLED=true
- RUN
- TEST_PKG
- TEST_RUN
- TEST_DIRS
- TOKEN_LIST
- UPHOLD_ACCESS_TOKEN
- "DAPP_ALLOWED_CORS_ORIGINS=https://my-dapp.com"
Expand Down
10 changes: 9 additions & 1 deletion libs/clients/coingecko/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/url"
"os"
"testing"
"time"

"github.com/brave-intl/bat-go/libs/clients/coingecko"
appctx "github.com/brave-intl/bat-go/libs/context"
Expand Down Expand Up @@ -54,7 +55,7 @@ func (suite *CoingeckoTestSuite) SetupTest() {
// vs-currency limit
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoVsCurrencyLimitCTXKey, coingeckoCurrencyLimit)

redisAddr := "redis://grant-redis:6379"
redisAddr := "redis://grant-redis:6379/1"
if len(os.Getenv("REDIS_ADDR")) > 0 {
redisAddr = os.Getenv("REDIS_ADDR")
}
Expand All @@ -71,6 +72,13 @@ func (suite *CoingeckoTestSuite) SetupTest() {
suite.Require().NoError(err, "Must be able to correctly initialize the client")
}

func (suite *CoingeckoTestSuite) TearDownTest() {
// flush all keys from the test Redis database
suite.Assert().NoError(suite.redis.FlushDB(suite.ctx).Err(), "Must be able to flush Redis database")
// work around Coingecko rate limit
time.Sleep(200 * time.Millisecond)
}

func (suite *CoingeckoTestSuite) TestFetchSimplePrice() {
resp, err := suite.client.FetchSimplePrice(suite.ctx, "basic-attention-token", "usd", false)
suite.Require().NoError(err, "should be able to fetch the simple price")
Expand Down
2 changes: 1 addition & 1 deletion libs/datastore/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ var (
}
dbs = map[string]*sqlx.DB{}
// CurrentMigrationVersion holds the default migration version
CurrentMigrationVersion = uint(69)
CurrentMigrationVersion = uint(70)
// MigrationTracks holds the migration version for a given track (eyeshade, promotion, wallet)
MigrationTracks = map[string]uint{
"eyeshade": 20,
Expand Down
2 changes: 1 addition & 1 deletion libs/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require (
github.com/golang/mock v1.6.0
github.com/google/go-querystring v1.1.0
github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.7
github.com/lib/pq v1.10.9
github.com/linkedin/goavro v2.1.0+incompatible
github.com/mssola/user_agent v0.5.3
github.com/patrickmn/go-cache v2.1.0+incompatible
Expand Down
2 changes: 2 additions & 0 deletions libs/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,8 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/linkedin/goavro v2.1.0+incompatible h1:DV2aUlj2xZiuxQyvag8Dy7zjY69ENjS66bWkSfdpddY=
github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
Expand Down
1 change: 1 addition & 0 deletions migrations/0070_create_solana_waitlist.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS solana_waitlist;
4 changes: 4 additions & 0 deletions migrations/0070_create_solana_waitlist.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE solana_waitlist (
payment_id uuid PRIMARY KEY,
joined_at TIMESTAMP NOT NULL
)
1 change: 1 addition & 0 deletions services/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.9
github.com/linkedin/goavro v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.13.0
github.com/redis/go-redis/v9 v9.7.0
github.com/rs/zerolog v1.28.0
Expand Down
9 changes: 8 additions & 1 deletion services/grant/cmd/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,14 @@ func setupRouter(ctx context.Context, logger *zerolog.Logger) (context.Context,
if len(dappAO) == 0 {
logger.Panic().Msg("dapp origin env missing")
}
r = wallet.RegisterRoutes(ctx, walletService, r, middleware.InstrumentHandler, wallet.NewDAppCorsMw(dappAO))

origins := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
dbg, _ := strconv.ParseBool(os.Getenv("DEBUG"))

corsOpts := wallet.NewCORSOpts(origins, dbg)
solMw := wallet.NewCORSMwr(corsOpts, http.MethodPost, http.MethodDelete)

r = wallet.RegisterRoutes(ctx, walletService, r, middleware.InstrumentHandler, wallet.NewDAppCorsMw(dappAO), solMw)

promotionDB, promotionRODB, err := promotion.NewPostgres()
if err != nil {
Expand Down
105 changes: 98 additions & 7 deletions services/ratios/controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/brave-intl/bat-go/libs/clients/stripe"
mockstripe "github.com/brave-intl/bat-go/libs/clients/stripe/mock"
appctx "github.com/brave-intl/bat-go/libs/context"
"github.com/brave-intl/bat-go/libs/inputs"
logutils "github.com/brave-intl/bat-go/libs/logging"
"github.com/brave-intl/bat-go/services/ratios"
"github.com/go-chi/chi"
Expand All @@ -36,6 +37,7 @@ type ControllersTestSuite struct {

ctx context.Context
service *ratios.Service
redis *redis.Client
mockCtrl *gomock.Controller
mockCoingeckoClient *mockcoingecko.MockClient
mockStripeClient *mockstripe.MockClient
Expand Down Expand Up @@ -66,12 +68,21 @@ func (suite *ControllersTestSuite) SetupSuite() {
// vs-currency limit
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoVsCurrencyLimitCTXKey, 2)
// all this is setup in init service
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoSymbolToIDCTXKey, map[string]string{"bat": "basic-attention-token"})
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoSymbolToIDCTXKey, map[string]string{
"bat": "basic-attention-token",
"eth": "ethereum",
})
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoContractToIDCTXKey, map[string]string{})
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoIDToSymbolCTXKey, map[string]string{"basic-attention-token": "bat"})
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoSupportedVsCurrenciesCTXKey, map[string]bool{"usd": true})
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoIDToSymbolCTXKey, map[string]string{
"basic-attention-token": "bat",
"ethereum": "eth",
})
suite.ctx = context.WithValue(suite.ctx, appctx.CoingeckoSupportedVsCurrenciesCTXKey, map[string]bool{
"usd": true,
"eur": true,
})

var redisAddr string = "redis://grant-redis:6379"
var redisAddr string = "redis://grant-redis:6379/2"
if len(os.Getenv("REDIS_ADDR")) > 0 {
redisAddr = os.Getenv("REDIS_ADDR")
}
Expand All @@ -90,9 +101,9 @@ func (suite *ControllersTestSuite) BeforeTest(sn, tn string) {
opts, err := redis.ParseURL(redisAddr)
suite.Require().NoError(err, "Must be able to parse redis URL")

redis := redis.NewClient(opts)
suite.redis = redis.NewClient(opts)

if err := redis.Ping(suite.ctx).Err(); err != nil {
if err := suite.redis.Ping(suite.ctx).Err(); err != nil {
suite.Require().NoError(err, "Must be able to ping redis")
}

Expand All @@ -102,14 +113,19 @@ func (suite *ControllersTestSuite) BeforeTest(sn, tn string) {
stripe := mockstripe.NewMockClient(suite.mockCtrl)
suite.mockStripeClient = stripe

suite.service = ratios.NewService(suite.ctx, coingecko, stripe, redis)
suite.service = ratios.NewService(suite.ctx, coingecko, stripe, suite.redis)
suite.Require().NoError(err, "failed to setup ratios service")
}

func (suite *ControllersTestSuite) AfterTest(sn, tn string) {
suite.mockCtrl.Finish()
}

func (suite *ControllersTestSuite) TearDownTest() {
// flush all keys from the test Redis database
suite.Assert().NoError(suite.redis.FlushDB(suite.ctx).Err(), "Must be able to flush Redis database")
}

func (suite *ControllersTestSuite) TestGetHistoryHandler() {
handler := ratios.GetHistoryHandler(suite.service)
req, err := http.NewRequest("GET", "/v2/history/coingecko/{coinID}/{vsCurrency}/{duration}", nil)
Expand Down Expand Up @@ -618,3 +634,78 @@ func (suite *ControllersTestSuite) TestCreateStripeOnrampSessionsHandler() {
suite.Require().Equal(ratiosResp.URL, "https://example.com")
}
}

func (suite *ControllersTestSuite) TestCacheOperations() {
// Initialize coins
var coinList ratios.CoingeckoCoinList
err := inputs.DecodeAndValidate(suite.ctx, &coinList, []byte("bat,eth"))
suite.Require().NoError(err)

// Test RecordCoinsAndCurrencies
err = suite.service.RecordCoinsAndCurrencies(
suite.ctx,
[]ratios.CoingeckoCoin(coinList),
[]ratios.CoingeckoVsCurrency{"usd", "eur"},
)
suite.Require().NoError(err, "Should record coins and currencies without error")

// Test GetTopCoins
topCoins, err := suite.service.GetTopCoins(suite.ctx, 10)
suite.Require().NoError(err, "Should get top coins without error")
suite.Require().Len(topCoins, 2, "Should have exactly 2 top coins (BAT, ETH)")
suite.Require().Contains(topCoins.String(), "basic-attention-token", "Should contain BAT in top coins")
suite.Require().Contains(topCoins.String(), "ethereum", "Should contain ETH in top coins")

// Test GetTopCurrencies
topCurrencies, err := suite.service.GetTopCurrencies(suite.ctx, 10)
suite.Require().NoError(err, "Should get top currencies without error")
suite.Require().Len(topCurrencies, 2, "Should have exactly 2 top currencies (USD, EUR)")
suite.Require().Contains(topCurrencies.String(), "usd", "Should contain USD in top currencies")
suite.Require().Contains(topCurrencies.String(), "eur", "Should contain EUR in top currencies")

// Test RunNextRelativeCachePrepopulationJob
// Setup mock response for FetchSimplePrice
mockResp := coingecko.SimplePriceResponse(map[string]map[string]decimal.Decimal{
"basic-attention-token": {
"usd": decimal.NewFromFloat(0.25),
"usd_24h_change": decimal.NewFromFloat(5.25),
},
"ethereum": {
"usd": decimal.NewFromFloat(2000.50),
"usd_24h_change": decimal.NewFromFloat(2.75),
},
})
suite.mockCoingeckoClient.EXPECT().
FetchSimplePrice(gomock.Any(), gomock.Any(), gomock.Any(), true).
Return(&mockResp, nil).
Times(1) // Expect exactly one call

// Run the job
ran, err := suite.service.RunNextRelativeCachePrepopulationJob(suite.ctx)
suite.Require().NoError(err, "Should run cache prepopulation job without error")
suite.Require().True(ran, "Should indicate job was run")

// Verify the data was cached by trying to retrieve it - this should NOT call Coingecko
rates, updated, err := suite.service.GetRelativeFromCache(
suite.ctx,
ratios.CoingeckoVsCurrencyList{"usd"},
[]ratios.CoingeckoCoin(coinList)...,
)
suite.Require().NoError(err, "Should get cached rates without error")
suite.Require().NotNil(rates, "Should have cached rates")
suite.Require().NotZero(updated, "Should have last updated timestamp")

// Verify the cached data matches what we expect
suite.Require().Contains((*rates)["basic-attention-token"], "usd")
suite.Require().Contains((*rates)["ethereum"], "usd")
suite.Require().Equal(
decimal.NewFromFloat(0.25),
(*rates)["basic-attention-token"]["usd"],
"BAT/USD rate should match",
)
suite.Require().Equal(
decimal.NewFromFloat(2000.50),
(*rates)["ethereum"]["usd"],
"ETH/USD rate should match",
)
}
4 changes: 2 additions & 2 deletions services/skus/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ func getOrderCredsByID(svc *Service, legacyMode bool) handlers.AppHandler {
if legacyMode {
suCreds, ok := creds.([]OrderCreds)
if !ok {
l.Err(errTypeAssertion).Str("orderID", orderID.String()).Str("itemID", itemIDv.String()).Int("status", status).Msg("error getting credentials type assertion")
l.Err(errTypeAssertion).Str("orderID", orderID.String()).Str("itemID", itemIDv.String()).Int("status", http.StatusInternalServerError).Msg("error getting credentials type assertion")
return handlers.WrapError(err, "Error getting credentials", http.StatusInternalServerError)
}

Expand All @@ -837,7 +837,7 @@ func getOrderCredsByID(svc *Service, legacyMode bool) handlers.AppHandler {
}
}

l.Err(errNotFound).Str("orderID", orderID.String()).Str("itemID", itemIDv.String()).Int("status", status).Msg("error finding creds legacy")
l.Err(errNotFound).Str("orderID", orderID.String()).Str("itemID", itemIDv.String()).Int("status", http.StatusNotFound).Msg("error finding creds legacy")

return handlers.WrapError(err, "Error getting credentials", http.StatusNotFound)
}
Expand Down
Loading
Loading