From fc02da88d63ea1d1bcaf27267693955527dc2331 Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 18 Jun 2024 11:09:33 +0300 Subject: [PATCH 01/22] Add inverted filter by event type --- ...ntegrations@rarime-points-svc@v1@public@events.yaml | 10 ++++++++++ internal/data/events.go | 1 + internal/data/pg/events.go | 7 +++++++ internal/service/handlers/list_events.go | 1 + internal/service/requests/list_events.go | 1 + 5 files changed, 20 insertions(+) diff --git a/docs/spec/paths/integrations@rarime-points-svc@v1@public@events.yaml b/docs/spec/paths/integrations@rarime-points-svc@v1@public@events.yaml index 00249fd..85ddae2 100644 --- a/docs/spec/paths/integrations@rarime-points-svc@v1@public@events.yaml +++ b/docs/spec/paths/integrations@rarime-points-svc@v1@public@events.yaml @@ -28,6 +28,16 @@ get: items: type: string example: "passport_scan" + - in: query + name: 'filter[meta.static.name][not]' + description: | + Inverted filter by event type name: excludes provided values + required: false + schema: + type: array + items: + type: string + example: "referral_specific" - in: query name: 'filter[has_expiration]' description: Filter events by type which has or hasn't expiration. diff --git a/internal/data/events.go b/internal/data/events.go index 2cba7cd..cf2c62f 100644 --- a/internal/data/events.go +++ b/internal/data/events.go @@ -61,6 +61,7 @@ type EventsQ interface { FilterByNullifier(string) EventsQ FilterByStatus(...EventStatus) EventsQ FilterByType(...string) EventsQ + FilterByNotType(types ...string) EventsQ FilterByUpdatedAtBefore(int64) EventsQ FilterByExternalID(string) EventsQ FilterInactiveNotClaimed(types ...string) EventsQ diff --git a/internal/data/pg/events.go b/internal/data/pg/events.go index 5ac1550..265cb87 100644 --- a/internal/data/pg/events.go +++ b/internal/data/pg/events.go @@ -208,6 +208,13 @@ func (q *events) FilterByType(types ...string) data.EventsQ { return q.applyCondition(squirrel.Eq{"type": types}) } +func (q *events) FilterByNotType(types ...string) data.EventsQ { + if len(types) == 0 { + return q + } + return q.applyCondition(squirrel.NotEq{"type": types}) +} + func (q *events) FilterByExternalID(id string) data.EventsQ { return q.applyCondition(squirrel.Eq{"external_id": id}) } diff --git a/internal/service/handlers/list_events.go b/internal/service/handlers/list_events.go index 42e4659..7cf1d72 100644 --- a/internal/service/handlers/list_events.go +++ b/internal/service/handlers/list_events.go @@ -50,6 +50,7 @@ func ListEvents(w http.ResponseWriter, r *http.Request) { FilterByNullifier(*req.FilterNullifier). FilterByStatus(req.FilterStatus...). FilterByType(req.FilterType...). + FilterByNotType(req.FilterNotType...). FilterInactiveNotClaimed(inactiveTypes...). Page(&req.OffsetPageParams). Select() diff --git a/internal/service/requests/list_events.go b/internal/service/requests/list_events.go index b30cc2a..340f4dd 100644 --- a/internal/service/requests/list_events.go +++ b/internal/service/requests/list_events.go @@ -16,6 +16,7 @@ type ListEvents struct { FilterStatus []data.EventStatus `filter:"status"` FilterType []string `filter:"meta.static.name"` FilterHasExpiration *bool `filter:"has_expiration"` + FilterNotType []string `url:"filter[meta.static.name][not]"` Count bool `url:"count"` } From 64f7c680b3dcb5232f770c5345d444e88961b92f Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 18 Jun 2024 12:00:53 +0300 Subject: [PATCH 02/22] Add inverted filter also for public event types --- ...ations@rarime-points-svc@v1@public@event_types.yaml | 10 ++++++++++ internal/service/handlers/list_event_types.go | 3 +++ internal/service/requests/list_event_types.go | 9 ++++++--- internal/service/requests/list_events.go | 9 +++++---- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/spec/paths/integrations@rarime-points-svc@v1@public@event_types.yaml b/docs/spec/paths/integrations@rarime-points-svc@v1@public@event_types.yaml index d04e10a..e3805ab 100644 --- a/docs/spec/paths/integrations@rarime-points-svc@v1@public@event_types.yaml +++ b/docs/spec/paths/integrations@rarime-points-svc@v1@public@event_types.yaml @@ -17,6 +17,16 @@ get: items: type: string example: "passport_scan" + - in: query + name: 'filter[name][not]' + description: | + Inverted filter by type name: excludes provided values + required: false + schema: + type: array + items: + type: string + example: "referral_specific" - in: query name: 'filter[flag]' description: Filter by configuration flags. Values are disjunctive (OR). diff --git a/internal/service/handlers/list_event_types.go b/internal/service/handlers/list_event_types.go index 0e883b5..8ebbf8b 100644 --- a/internal/service/handlers/list_event_types.go +++ b/internal/service/handlers/list_event_types.go @@ -20,6 +20,9 @@ func ListEventTypes(w http.ResponseWriter, r *http.Request) { types := EventTypes(r).List( evtypes.FilterByNames(req.FilterName...), evtypes.FilterByFlags(req.FilterFlag...), + func(ev evtypes.EventConfig) bool { + return len(req.FilterNotName) > 0 && !evtypes.FilterByNames(req.FilterNotName...)(ev) + }, ) resTypes := make([]resources.EventType, len(types)) diff --git a/internal/service/requests/list_event_types.go b/internal/service/requests/list_event_types.go index 10054f8..1a65954 100644 --- a/internal/service/requests/list_event_types.go +++ b/internal/service/requests/list_event_types.go @@ -9,8 +9,9 @@ import ( ) type ListExpiredEvents struct { - FilterName []string `filter:"name"` - FilterFlag []string `filter:"flag"` + FilterName []string `filter:"name"` + FilterFlag []string `filter:"flag"` + FilterNotName []string `url:"filter[name][not]"` } func NewListEventTypes(r *http.Request) (req ListExpiredEvents, err error) { @@ -25,7 +26,9 @@ func NewListEventTypes(r *http.Request) (req ListExpiredEvents, err error) { evtypes.FlagNotStarted, evtypes.FlagExpired, evtypes.FlagDisabled, - )))}.Filter() + ))), + "filter[name][not]": val.Validate(req.FilterNotName, val.When(len(req.FilterName) > 0, val.Nil, val.Empty)), + }.Filter() return } diff --git a/internal/service/requests/list_events.go b/internal/service/requests/list_events.go index 340f4dd..80e3fbf 100644 --- a/internal/service/requests/list_events.go +++ b/internal/service/requests/list_events.go @@ -4,7 +4,7 @@ import ( "net/http" "strings" - validation "github.com/go-ozzo/ozzo-validation/v4" + val "github.com/go-ozzo/ozzo-validation/v4" "github.com/rarimo/rarime-points-svc/internal/data" "github.com/rarimo/rarime-points-svc/internal/service/page" "gitlab.com/distributed_lab/urlval/v4" @@ -33,9 +33,10 @@ func NewListEvents(r *http.Request) (req ListEvents, err error) { *req.FilterNullifier = strings.ToLower(*req.FilterNullifier) } - err = validation.Errors{ - "filter[nullifier]": validation.Validate(req.FilterNullifier, validation.Required, validation.Match(nullifierRegexp)), - "filter[status]": validation.Validate(req.FilterStatus, validation.Each(validation.In(data.EventOpen, data.EventFulfilled, data.EventClaimed))), + err = val.Errors{ + "filter[nullifier]": val.Validate(req.FilterNullifier, val.Required, val.Match(nullifierRegexp)), + "filter[status]": val.Validate(req.FilterStatus, val.Each(val.In(data.EventOpen, data.EventFulfilled, data.EventClaimed))), + "filter[meta.static.name][not]": val.Validate(req.FilterNotType, val.When(len(req.FilterType) > 0, val.Nil, val.Empty)), }.Filter() return } From 5cca7133f5a9cb030c25f2f990da4e3e32b771b1 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Tue, 18 Jun 2024 13:35:17 +0300 Subject: [PATCH 03/22] Add auxiliary test functions --- requests_test.go | 93 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/requests_test.go b/requests_test.go index 4f5747c..ac452b6 100644 --- a/requests_test.go +++ b/requests_test.go @@ -7,6 +7,7 @@ import ( "log" "net/http" "net/url" + "os" "strings" "testing" "time" @@ -27,12 +28,50 @@ const ( gbrCode = "4670034" deuCode = "4474197" - genesisCode = "kPRQYQUcWzW" + genesisBalance = "0x0000000000000000000000000000000000000000000000000000000000000000" balancesEndpoint = "public/balances" eventsEndpoint = "public/events" ) +var ( + apiURL = "" + genesisCode = "" +) + +var ( + balancesPath = func() string { + return apiURL + "public/balances" + } + balancesSpecificPath = func(nullifier string) string { + return apiURL + "public/balances/" + nullifier + } + verifyPassportPath = func(nullifier string) string { + return balancesSpecificPath(nullifier) + "/verifypassport" + } + witdhrawalsPath = func(nullifier string) string { + return balancesSpecificPath(nullifier) + "/withdrawals" + } + countriesConfigPath = func() string { + return apiURL + "public/countries_config" + } + eventTypesPath = func() string { + return apiURL + "public/event_types" + } + eventsPath = func() string { + return apiURL + "public/events" + } + eventsSpecificPath = func(id string) string { + return apiURL + "public/events/" + id + } + fulfillEventPath = func() string { + return apiURL + "private/events" + } + editReferralsPath = func() string { + return apiURL + "private/referrals" + } +) + var baseProof = zkptypes.ZKProof{ Proof: &zkptypes.ProofData{ A: []string{"0", "0", "0"}, @@ -43,6 +82,45 @@ var baseProof = zkptypes.ZKProof{ PubSignals: []string{"0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"}, } +func TestMain(m *testing.M) { + if err := setUp(); err != nil { + panic(fmt.Errorf("failed to setup: %w", err)) + } + + exitVal := m.Run() + os.Exit(exitVal) + + if err := tearDown(); err != nil { + panic(fmt.Errorf("failed to teardown: %w", err)) + } +} + +func setUp() error { + if err := setApiURL(); err != nil { + return fmt.Errorf("failed to set api URL: %w", err) + } + + return nil +} + +func tearDown() error { + return nil +} + +func setApiURL() error { + var cfg struct { + Addr string `fig:"addr,required"` + } + + err := figure.Out(&cfg).From(kv.MustGetStringMap(kv.MustFromEnv(), "listener")).Please() + if err != nil { + return fmt.Errorf("failed to figure out listener from service config", err) + } + + apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1/", cfg.Addr) + return nil +} + func TestCreateBalance(t *testing.T) { t.Run("SimpleBalance", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" @@ -381,6 +459,8 @@ func getBalance(t *testing.T, nullifier string) resources.BalanceResponse { return balance } +func editReferrals(t *testing.T) + func verifyPassportBody(nullifier string, proof zkptypes.ZKProof) resources.VerifyPassportRequest { return resources.VerifyPassportRequest{ Data: resources.VerifyPassport{ @@ -492,14 +572,3 @@ func getRequest(t *testing.T, endpoint string, query url.Values, user string) ([ return respBody, resp.StatusCode } - -var apiURL = func() string { - var cfg struct { - Addr string `fig:"addr,required"` - } - err := figure.Out(&cfg).From(kv.MustGetStringMap(kv.MustFromEnv(), "listener")).Please() - if err != nil { - panic(err) - } - return fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1/", cfg.Addr) -}() From cffb028552e4ab89770e897ed2e545fe53feb0c2 Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 18 Jun 2024 15:58:44 +0300 Subject: [PATCH 04/22] Reduce boilerplate code in testing request functions --- requests_test.go | 244 +++++++++++++++++++---------------------------- 1 file changed, 100 insertions(+), 144 deletions(-) diff --git a/requests_test.go b/requests_test.go index ac452b6..9010760 100644 --- a/requests_test.go +++ b/requests_test.go @@ -1,28 +1,32 @@ package main_test import ( + "bytes" "encoding/json" + "errors" "fmt" "io" "log" "net/http" "net/url" "os" - "strings" + "runtime/debug" "testing" "time" zkptypes "github.com/iden3/go-rapidsnark/types" + "github.com/rarimo/rarime-points-svc/internal/config" "github.com/rarimo/rarime-points-svc/internal/data/evtypes" + "github.com/rarimo/rarime-points-svc/internal/service/requests" "github.com/rarimo/rarime-points-svc/resources" zk "github.com/rarimo/zkverifier-kit" - "gitlab.com/distributed_lab/figure" "gitlab.com/distributed_lab/kit/kv" ) -const requestTimeout = time.Second // use bigger on debug with breakpoints to prevent fails - const ( + requestTimeout = time.Second // use bigger on debug with breakpoints to prevent fails + defaultConfigFile = "config.local.yaml" + ukrCode = "5589842" usaCode = "5591873" gbrCode = "4670034" @@ -35,41 +39,9 @@ const ( ) var ( - apiURL = "" - genesisCode = "" -) - -var ( - balancesPath = func() string { - return apiURL + "public/balances" - } - balancesSpecificPath = func(nullifier string) string { - return apiURL + "public/balances/" + nullifier - } - verifyPassportPath = func(nullifier string) string { - return balancesSpecificPath(nullifier) + "/verifypassport" - } - witdhrawalsPath = func(nullifier string) string { - return balancesSpecificPath(nullifier) + "/withdrawals" - } - countriesConfigPath = func() string { - return apiURL + "public/countries_config" - } - eventTypesPath = func() string { - return apiURL + "public/event_types" - } - eventsPath = func() string { - return apiURL + "public/events" - } - eventsSpecificPath = func(id string) string { - return apiURL + "public/events/" + id - } - fulfillEventPath = func() string { - return apiURL + "private/events" - } - editReferralsPath = func() string { - return apiURL + "private/referrals" - } + globalCfg config.Config + apiURL string + genesisCode string ) var baseProof = zkptypes.ZKProof{ @@ -83,49 +55,43 @@ var baseProof = zkptypes.ZKProof{ } func TestMain(m *testing.M) { - if err := setUp(); err != nil { - panic(fmt.Errorf("failed to setup: %w", err)) - } - - exitVal := m.Run() - os.Exit(exitVal) + var exitVal int + defer func() { + if r := recover(); r != nil { + log.Printf("tests panicked: %v\n%s", r, debug.Stack()) + exitVal = 1 + } + os.Exit(exitVal) + }() - if err := tearDown(); err != nil { - panic(fmt.Errorf("failed to teardown: %w", err)) - } + setUp() + exitVal = m.Run() + tearDown() } -func setUp() error { - if err := setApiURL(); err != nil { - return fmt.Errorf("failed to set api URL: %w", err) +func setUp() { + if os.Getenv(kv.EnvViperConfigFile) == "" { + err := os.Setenv(kv.EnvViperConfigFile, defaultConfigFile) + if err != nil { + panic(fmt.Errorf("failed to set env: %w", err)) + } } - return nil -} - -func tearDown() error { - return nil -} + globalCfg = config.New(kv.MustFromEnv()) + apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1", globalCfg.Listener().Addr().String()) -func setApiURL() error { - var cfg struct { - Addr string `fig:"addr,required"` - } - - err := figure.Out(&cfg).From(kv.MustGetStringMap(kv.MustFromEnv(), "listener")).Please() + refs, err := editReferrals(genesisBalance, 15) if err != nil { - return fmt.Errorf("failed to figure out listener from service config", err) + panic(fmt.Errorf("failed to edit referrals: %w", err)) } - - apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1/", cfg.Addr) - return nil + genesisCode = refs.Ref } func TestCreateBalance(t *testing.T) { t.Run("SimpleBalance", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" body := createBalanceBody(nullifier, genesisCode) - _, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + _, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) if respCode != http.StatusOK { t.Errorf("failed to create simple balance: want %d got %d", http.StatusOK, respCode) } @@ -134,7 +100,7 @@ func TestCreateBalance(t *testing.T) { t.Run("SameBalance", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" body := createBalanceBody(nullifier, genesisCode) - _, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + _, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) if respCode != http.StatusConflict { t.Errorf("want %d got %d", http.StatusConflict, respCode) } @@ -143,7 +109,7 @@ func TestCreateBalance(t *testing.T) { t.Run("Unauthorized", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" body := createBalanceBody(nullifier, genesisCode) - _, respCode := postPatchRequest(t, balancesEndpoint, body, "0x1"+nullifier[3:], false) + _, respCode := requestWithBody(t, balancesEndpoint, body, "0x1"+nullifier[3:], false) if respCode != http.StatusUnauthorized { t.Errorf("want %d got %d", http.StatusUnauthorized, respCode) } @@ -152,7 +118,7 @@ func TestCreateBalance(t *testing.T) { t.Run("IncorrectCode", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" body := createBalanceBody(nullifier, "someAntoherCode") - _, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + _, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) if respCode != http.StatusNotFound { t.Errorf("want %d got %d", http.StatusNotFound, respCode) } @@ -175,14 +141,14 @@ func TestVerifyPassport(t *testing.T) { body := verifyPassportBody(nullifier, proof) t.Run("VerifyPassport", func(t *testing.T) { - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) + _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) if respCode != http.StatusNoContent { t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) } }) t.Run("VerifyOneMore", func(t *testing.T) { - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) + _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) if respCode != http.StatusTooManyRequests { t.Errorf("want %d got %d", http.StatusTooManyRequests, respCode) } @@ -191,7 +157,7 @@ func TestVerifyPassport(t *testing.T) { t.Run("IncorrectCountryCode", func(t *testing.T) { proof.PubSignals[zk.Citizenship] = "6974819" body = verifyPassportBody(referrer, proof) - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+referrer+"/verifypassport", body, referrer, false) + _, respCode := requestWithBody(t, balancesEndpoint+"/"+referrer+"/verifypassport", body, referrer, false) if respCode != http.StatusInternalServerError { t.Errorf("want %d got %d", http.StatusInternalServerError, respCode) } @@ -215,7 +181,7 @@ func TestClaimEvent(t *testing.T) { t.Run("TryClaimOpenEvent", func(t *testing.T) { body := claimEventBody(passportScanEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+passportScanEventID, body, nullifier1, true) + _, respCode := requestWithBody(t, eventsEndpoint+"/"+passportScanEventID, body, nullifier1, true) if respCode != http.StatusNotFound { t.Errorf("want %d got %d", http.StatusNotFound, respCode) } @@ -231,7 +197,7 @@ func TestClaimEvent(t *testing.T) { t.Run("TryClaimEventWithoutPassport", func(t *testing.T) { body := claimEventBody(refSpecEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+refSpecEventID, body, nullifier1, true) + _, respCode := requestWithBody(t, eventsEndpoint+"/"+refSpecEventID, body, nullifier1, true) if respCode != http.StatusForbidden { t.Errorf("want %d got %d", http.StatusForbidden, respCode) } @@ -244,7 +210,7 @@ func TestClaimEvent(t *testing.T) { t.Run("ClaimEvent", func(t *testing.T) { body := claimEventBody(passportScanEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+passportScanEventID, body, nullifier2, true) + _, respCode := requestWithBody(t, eventsEndpoint+"/"+passportScanEventID, body, nullifier2, true) if respCode != http.StatusOK { t.Errorf("want %d got %d", http.StatusOK, respCode) } @@ -313,7 +279,7 @@ func TestCountryPools(t *testing.T) { } body := claimEventBody(freeWeeklyEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) + _, respCode := requestWithBody(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) if respCode != http.StatusForbidden { t.Errorf("want %d got %d", http.StatusForbidden, respCode) } @@ -331,7 +297,7 @@ func TestCountryPools(t *testing.T) { } body := claimEventBody(freeWeeklyEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) + _, respCode := requestWithBody(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) if respCode != http.StatusForbidden { t.Errorf("want %d got %d", http.StatusForbidden, respCode) } @@ -358,7 +324,7 @@ func TestCountryPools(t *testing.T) { } body := claimEventBody(freeWeeklyEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) + _, respCode := requestWithBody(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) if respCode != http.StatusForbidden { t.Errorf("want %d got %d", http.StatusForbidden, respCode) } @@ -376,7 +342,7 @@ func getEventFromList(events resources.EventListResponse, evtype string) (id, st func claimEvent(t *testing.T, id, nullifier string) resources.EventResponse { body := claimEventBody(id) - respBody, respCode := postPatchRequest(t, eventsEndpoint+"/"+id, body, nullifier, true) + respBody, respCode := requestWithBody(t, eventsEndpoint+"/"+id, body, nullifier, true) if respCode != http.StatusOK { t.Errorf("want %d got %d", http.StatusOK, respCode) } @@ -395,7 +361,7 @@ func verifyPassport(t *testing.T, nullifier, country string) { proof.PubSignals[zk.Citizenship] = country body := verifyPassportBody(nullifier, proof) - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) + _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) if respCode != http.StatusNoContent { t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) } @@ -423,7 +389,7 @@ func getEvents(t *testing.T, nullifier string) resources.EventListResponse { func createBalance(t *testing.T, nullifier, code string) resources.BalanceResponse { body := createBalanceBody(nullifier, code) - respBody, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + respBody, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) if respCode != http.StatusOK { t.Fatalf("failed to create simple balance: want %d got %d", http.StatusOK, respCode) } @@ -437,21 +403,17 @@ func createBalance(t *testing.T, nullifier, code string) resources.BalanceRespon return balance } -func getBalance(t *testing.T, nullifier string) resources.BalanceResponse { - respBody, respCode := getRequest(t, - balancesEndpoint+"/"+nullifier, - func() url.Values { - query := url.Values{} - query.Add("referral_codes", "true") - query.Add("rank", "true") - return query - }(), nullifier) - if respCode != http.StatusOK { - t.Errorf("failed to get balance: want %d got %d", http.StatusOK, respCode) +func getBalance(t *testing.T, nullifier string) (balance resources.BalanceResponse, err error) { + query := url.Values{} + query.Add("referral_codes", "true") + query.Add("rank", "true") + + respBody, err := getRequest(balancesEndpoint+"/"+nullifier, query, nullifier) + if err != nil { + return } - var balance resources.BalanceResponse - err := json.Unmarshal(respBody, &balance) + err = json.Unmarshal(respBody, &balance) if err != nil { t.Fatalf("failed to unmarhal balance response: %v", err) } @@ -459,7 +421,18 @@ func getBalance(t *testing.T, nullifier string) resources.BalanceResponse { return balance } -func editReferrals(t *testing.T) +type editReferralsResponse struct { + Ref string `json:"referral"` + UsageLeft uint64 `json:"usage_left"` +} + +func editReferrals(nullifier string, count uint64) (resp editReferralsResponse, err error) { + req := requests.EditReferralsRequest{Nullifier: nullifier, Count: count} + + err = requestWithBody(apiURL+"/private/referrals", "POST", "", req, &resp) + + return +} func verifyPassportBody(nullifier string, proof zkptypes.ZKProof) resources.VerifyPassportRequest { return resources.VerifyPassportRequest{ @@ -498,77 +471,60 @@ func claimEventBody(id string) resources.Relation { } } -func postPatchRequest(t *testing.T, endpoint string, body any, user string, patch bool) ([]byte, int) { - if body == nil { - t.Fatal("request body not provided") - } +func requestWithBody(endpoint, method, user string, body, result any) error { bodyJSON, err := json.Marshal(body) if err != nil { - t.Fatalf("failed to marshal request bode: %v", err) + return fmt.Errorf("failed to marshal body: %w", err) } - log.Printf(" endpoint=/%s body=%s", endpoint, body) - - reqBody := strings.NewReader(string(bodyJSON)) - - reqType := "POST" - if patch { - reqType = "PATCH" - } - - req, err := http.NewRequest(reqType, apiURL+endpoint, reqBody) - if err != nil { - t.Fatalf("failed to create post request: %v", err) - } - - if user != "" { - req.Header.Set("nullifier", user) - } - - resp, err := (&http.Client{Timeout: requestTimeout}).Do(req) + reqBody := bytes.NewReader(bodyJSON) + req, err := http.NewRequest(method, apiURL+endpoint, reqBody) if err != nil { - t.Fatalf("failed to perform post request: %v", err) - } - defer func() { - resp.Body.Close() - }() - respBody, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("failed to read resp body: %v", err) + return fmt.Errorf("failed to create %s request: %w", method, err) } - log.Printf(" endpoint=/%s body=%s", endpoint, respBody) - - return respBody, resp.StatusCode + return doRequest(req, user, result) } -func getRequest(t *testing.T, endpoint string, query url.Values, user string) ([]byte, int) { - log.Printf(" endpoint=/%s query=%+v", endpoint, query) - +func getRequest(endpoint string, query url.Values, user string, result any) error { req, err := http.NewRequest("GET", apiURL+endpoint, nil) if err != nil { - t.Fatalf("failed to create get request: %v", err) + return fmt.Errorf("failed to create GET request: %w", err) } - req.URL.RawQuery = query.Encode() + return doRequest(req, user, result) +} + +func doRequest(req *http.Request, user string, result any) error { + reqLog := fmt.Sprintf("%s /%s?%s", req.Method, req.URL.Path, req.URL.Query().Encode()) + if user != "" { req.Header.Set("nullifier", user) } resp, err := (&http.Client{Timeout: requestTimeout}).Do(req) if err != nil { - t.Fatalf("failed to perform get request: %v", err) + return fmt.Errorf("failed to perform request (%s): %w", reqLog, err) + } + defer func() { _ = resp.Body.Close() }() + + log.Printf("Req: %s status=%d", reqLog, resp.StatusCode) + switch resp.StatusCode { + case http.StatusOK, http.StatusCreated, http.StatusNoContent: + default: + return errors.New("unsuccessful response code") } + respBody, err := io.ReadAll(resp.Body) if err != nil { - t.Fatalf("failed to read resp body: %v", err) + return fmt.Errorf("failed to read resp body: %w", err) } - defer func() { - resp.Body.Close() - }() - log.Printf(" endpoint=/%s body=%s", endpoint, respBody) + err = json.Unmarshal(respBody, result) + if err != nil { + return fmt.Errorf("failed to unmarshal response: %w", err) + } - return respBody, resp.StatusCode + return nil } From 35180cc8fc56080aa94cf8fb16a611046b7ee55b Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Tue, 18 Jun 2024 17:41:23 +0300 Subject: [PATCH 05/22] Fix auth in create balance. Fix points and level accruing after claim. --- internal/service/handlers/claim_event.go | 2 -- internal/service/handlers/create_balance.go | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/service/handlers/claim_event.go b/internal/service/handlers/claim_event.go index 30eab5d..d326d64 100644 --- a/internal/service/handlers/claim_event.go +++ b/internal/service/handlers/claim_event.go @@ -165,8 +165,6 @@ func DoClaimEventUpdates( if err = referralsQ.New().Insert(refToAdd...); err != nil { return fmt.Errorf("failed to insert referrals: %w", err) } - - return nil } err = balancesQ.FilterByNullifier(balance.Nullifier).Update(map[string]any{ diff --git a/internal/service/handlers/create_balance.go b/internal/service/handlers/create_balance.go index 1e85a64..7303837 100644 --- a/internal/service/handlers/create_balance.go +++ b/internal/service/handlers/create_balance.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/rarimo/decentralized-auth-svc/pkg/auth" "github.com/rarimo/rarime-points-svc/internal/data" "github.com/rarimo/rarime-points-svc/internal/data/evtypes" "github.com/rarimo/rarime-points-svc/internal/service/requests" @@ -21,6 +22,11 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { nullifier := req.Data.ID + if !auth.Authenticates(UserClaims(r), auth.UserGrant(nullifier)) { + ape.RenderErr(w, problems.Unauthorized()) + return + } + balance, err := BalancesQ(r).FilterByNullifier(nullifier).Get() if err != nil { Log(r).WithError(err).Error("Failed to get balance by nullifier") From 812c85ff39618d6887d98b2f6e3443418620e307 Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 18 Jun 2024 18:21:10 +0300 Subject: [PATCH 06/22] Refactor balance creation architecture --- requests_test.go | 149 ++++++++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/requests_test.go b/requests_test.go index 9010760..92102ab 100644 --- a/requests_test.go +++ b/requests_test.go @@ -2,8 +2,8 @@ package main_test import ( "bytes" + "crypto/sha256" "encoding/json" - "errors" "fmt" "io" "log" @@ -11,15 +11,20 @@ import ( "net/url" "os" "runtime/debug" + "strconv" "testing" "time" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/google/jsonapi" zkptypes "github.com/iden3/go-rapidsnark/types" "github.com/rarimo/rarime-points-svc/internal/config" "github.com/rarimo/rarime-points-svc/internal/data/evtypes" "github.com/rarimo/rarime-points-svc/internal/service/requests" "github.com/rarimo/rarime-points-svc/resources" zk "github.com/rarimo/zkverifier-kit" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gitlab.com/distributed_lab/kit/kv" ) @@ -39,15 +44,17 @@ const ( ) var ( - globalCfg config.Config - apiURL string - genesisCode string + globalCfg config.Config + apiURL string + genesisCode string + nullifiers []string + currentNullifierIndex int ) var baseProof = zkptypes.ZKProof{ Proof: &zkptypes.ProofData{ A: []string{"0", "0", "0"}, - B: []([]string){{"0", "0"}, {"0", "0"}, {"0", "0"}}, + B: [][]string{{"0", "0"}, {"0", "0"}, {"0", "0"}}, C: []string{"0", "0", "0"}, Protocol: "groth16", }, @@ -80,48 +87,85 @@ func setUp() { globalCfg = config.New(kv.MustFromEnv()) apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1", globalCfg.Listener().Addr().String()) - refs, err := editReferrals(genesisBalance, 15) + refs, err := editReferrals(genesisBalance, 20) if err != nil { panic(fmt.Errorf("failed to edit referrals: %w", err)) } genesisCode = refs.Ref + + nullifiers = make([]string, 20) + for i := range nullifiers { + hash := sha256.Sum256([]byte{byte(i)}) + nullifiers[i] = hexutil.Encode(hash[:]) + } } func TestCreateBalance(t *testing.T) { - t.Run("SimpleBalance", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" - body := createBalanceBody(nullifier, genesisCode) - _, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) - if respCode != http.StatusOK { - t.Errorf("failed to create simple balance: want %d got %d", http.StatusOK, respCode) - } + var ( + nullifierShared = nextN() + otRefCode string + ) + + validBalanceChecks := func(t *testing.T, nullifier, code string) { + resp, err := createBalance(nullifier, genesisCode) + require.NoError(t, err) + require.Equal(t, nullifier, resp.Data.ID) + + attr := resp.Data.Attributes + + require.NotNil(t, attr.IsDisabled) + require.NotNil(t, attr.IsVerified) + require.NotNil(t, attr.ReferralCodes) + require.NotEmpty(t, *attr.ReferralCodes) + + assert.Equal(t, 0, attr.Amount) + assert.False(t, *attr.IsDisabled) + assert.False(t, *attr.IsVerified) + assert.Equal(t, 1, attr.Level) + assert.NotNil(t, attr.Rank) + + otRefCode = (*attr.ReferralCodes)[0].Id + require.NotEmpty(t, otRefCode) + } + + // fixme @violog: looks like fail on assert/require won't stop outer tests, must check before proceeding + t.Run("BalanceGenesisCode", func(t *testing.T) { + validBalanceChecks(t, nullifierShared, genesisCode) }) - t.Run("SameBalance", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" - body := createBalanceBody(nullifier, genesisCode) - _, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) - if respCode != http.StatusConflict { - t.Errorf("want %d got %d", http.StatusConflict, respCode) - } + t.Run("BalanceOneTimeCode", func(t *testing.T) { + validBalanceChecks(t, nextN(), otRefCode) }) - t.Run("Unauthorized", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" - body := createBalanceBody(nullifier, genesisCode) - _, respCode := requestWithBody(t, balancesEndpoint, body, "0x1"+nullifier[3:], false) - if respCode != http.StatusUnauthorized { - t.Errorf("want %d got %d", http.StatusUnauthorized, respCode) - } + t.Run("SameBalanceConflict", func(t *testing.T) { + _, err := createBalance(nullifierShared, genesisCode) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, "409", apiErr.Status) + }) + + t.Run("NullifierUnauthorized", func(t *testing.T) { + n1, n2 := nextN(), nextN() + body := createBalanceBody(n1, genesisCode) + err := requestWithBody(balancesEndpoint, "POST", n2, body, nil) + + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, "401", apiErr.Status) + }) + + t.Run("ConsumedCode", func(t *testing.T) { + _, err := createBalance(nextN(), otRefCode) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, "404", apiErr.Status) }) t.Run("IncorrectCode", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" - body := createBalanceBody(nullifier, "someAntoherCode") - _, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) - if respCode != http.StatusNotFound { - t.Errorf("want %d got %d", http.StatusNotFound, respCode) - } + _, err := createBalance(nextN(), "invalid") + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, "404", apiErr.Status) }) } @@ -387,38 +431,19 @@ func getEvents(t *testing.T, nullifier string) resources.EventListResponse { return events } -func createBalance(t *testing.T, nullifier, code string) resources.BalanceResponse { +func createBalance(nullifier, code string) (resp resources.BalanceResponse, err error) { body := createBalanceBody(nullifier, code) - respBody, respCode := requestWithBody(t, balancesEndpoint, body, nullifier, false) - if respCode != http.StatusOK { - t.Fatalf("failed to create simple balance: want %d got %d", http.StatusOK, respCode) - } - - var balance resources.BalanceResponse - err := json.Unmarshal(respBody, &balance) - if err != nil { - t.Fatalf("failed to unmarhal balance response: %v", err) - } - - return balance + err = requestWithBody(balancesEndpoint, "POST", nullifier, body, &resp) + return } -func getBalance(t *testing.T, nullifier string) (balance resources.BalanceResponse, err error) { +func getBalance(nullifier string) (resp resources.BalanceResponse, err error) { query := url.Values{} query.Add("referral_codes", "true") query.Add("rank", "true") - respBody, err := getRequest(balancesEndpoint+"/"+nullifier, query, nullifier) - if err != nil { - return - } - - err = json.Unmarshal(respBody, &balance) - if err != nil { - t.Fatalf("failed to unmarhal balance response: %v", err) - } - - return balance + err = getRequest(balancesEndpoint+"/"+nullifier, query, nullifier, &resp) + return } type editReferralsResponse struct { @@ -510,10 +535,11 @@ func doRequest(req *http.Request, user string, result any) error { defer func() { _ = resp.Body.Close() }() log.Printf("Req: %s status=%d", reqLog, resp.StatusCode) + switch resp.StatusCode { case http.StatusOK, http.StatusCreated, http.StatusNoContent: default: - return errors.New("unsuccessful response code") + return &jsonapi.ErrorObject{Status: strconv.Itoa(resp.StatusCode)} } respBody, err := io.ReadAll(resp.Body) @@ -528,3 +554,8 @@ func doRequest(req *http.Request, user string, result any) error { return nil } + +func nextN() string { + currentNullifierIndex++ + return nullifiers[currentNullifierIndex-1] +} From 7e83574608c32e996b4bb89d14f983c165508db5 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Tue, 18 Jun 2024 18:53:53 +0300 Subject: [PATCH 07/22] Add AutoClaim test. Refactoring --- config.yaml | 18 ++ requests_test.go | 437 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 313 insertions(+), 142 deletions(-) diff --git a/config.yaml b/config.yaml index 818a968..08dccaf 100644 --- a/config.yaml +++ b/config.yaml @@ -18,6 +18,7 @@ event_types: frequency: one-time action_url: https://... logo: https://... + auto_claim: true - name: free_weekly title: Free weekly points reward: 1 @@ -44,6 +45,7 @@ event_types: description: The user {:did} has verified the passport. Claim the reward! short_description: Short description no_auto_open: true + auto_claim: true levels: levels: @@ -74,6 +76,22 @@ countries: reserve_limit: 5 reserve_allowed: false withdrawal_allowed: true + - code: "CAN" + reserve_limit: 100 + reserve_allowed: false + withdrawal_allowed: false + - code: "FRA" + reserve_limit: 6 + reserve_allowed: true + withdrawal_allowed: true + - code: "IND" + reserve_limit: 1 + reserve_allowed: false + withdrawal_allowed: true + - code: "MCO" + reserve_limit: 100 + reserve_allowed: false + withdrawal_allowed: false - code: "default" reserve_limit: 5 reserve_allowed: true diff --git a/requests_test.go b/requests_test.go index ac452b6..c3a8992 100644 --- a/requests_test.go +++ b/requests_test.go @@ -13,6 +13,7 @@ import ( "time" zkptypes "github.com/iden3/go-rapidsnark/types" + "github.com/rarimo/rarime-points-svc/internal/data" "github.com/rarimo/rarime-points-svc/internal/data/evtypes" "github.com/rarimo/rarime-points-svc/resources" zk "github.com/rarimo/zkverifier-kit" @@ -27,6 +28,10 @@ const ( usaCode = "5591873" gbrCode = "4670034" deuCode = "4474197" + canCode = "4407630" + fraCode = "4608577" + indCode = "4804164" + mcoCode = "5063503" genesisBalance = "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -36,15 +41,15 @@ const ( var ( apiURL = "" - genesisCode = "" + genesisCode = "kPRQYQUcWzW" ) var ( balancesPath = func() string { - return apiURL + "public/balances" + return "public/balances" } balancesSpecificPath = func(nullifier string) string { - return apiURL + "public/balances/" + nullifier + return "public/balances/" + nullifier } verifyPassportPath = func(nullifier string) string { return balancesSpecificPath(nullifier) + "/verifypassport" @@ -53,22 +58,22 @@ var ( return balancesSpecificPath(nullifier) + "/withdrawals" } countriesConfigPath = func() string { - return apiURL + "public/countries_config" + return "public/countries_config" } eventTypesPath = func() string { - return apiURL + "public/event_types" + return "public/event_types" } eventsPath = func() string { - return apiURL + "public/events" + return "public/events" } eventsSpecificPath = func(id string) string { - return apiURL + "public/events/" + id + return "public/events/" + id } fulfillEventPath = func() string { - return apiURL + "private/events" + return "private/events" } editReferralsPath = func() string { - return apiURL + "private/referrals" + return "private/referrals" } ) @@ -88,11 +93,11 @@ func TestMain(m *testing.M) { } exitVal := m.Run() - os.Exit(exitVal) if err := tearDown(); err != nil { panic(fmt.Errorf("failed to teardown: %w", err)) } + os.Exit(exitVal) } func setUp() error { @@ -114,7 +119,7 @@ func setApiURL() error { err := figure.Out(&cfg).From(kv.MustGetStringMap(kv.MustFromEnv(), "listener")).Please() if err != nil { - return fmt.Errorf("failed to figure out listener from service config", err) + return fmt.Errorf("failed to figure out listener from service config: %w", err) } apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1/", cfg.Addr) @@ -125,7 +130,7 @@ func TestCreateBalance(t *testing.T) { t.Run("SimpleBalance", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" body := createBalanceBody(nullifier, genesisCode) - _, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + _, respCode := postPatchRequest(t, balancesPath(), body, nullifier, false) if respCode != http.StatusOK { t.Errorf("failed to create simple balance: want %d got %d", http.StatusOK, respCode) } @@ -134,7 +139,7 @@ func TestCreateBalance(t *testing.T) { t.Run("SameBalance", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000001" body := createBalanceBody(nullifier, genesisCode) - _, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + _, respCode := postPatchRequest(t, balancesPath(), body, nullifier, false) if respCode != http.StatusConflict { t.Errorf("want %d got %d", http.StatusConflict, respCode) } @@ -143,7 +148,7 @@ func TestCreateBalance(t *testing.T) { t.Run("Unauthorized", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" body := createBalanceBody(nullifier, genesisCode) - _, respCode := postPatchRequest(t, balancesEndpoint, body, "0x1"+nullifier[3:], false) + _, respCode := postPatchRequest(t, balancesPath(), body, "0x1"+nullifier[3:], false) if respCode != http.StatusUnauthorized { t.Errorf("want %d got %d", http.StatusUnauthorized, respCode) } @@ -152,7 +157,7 @@ func TestCreateBalance(t *testing.T) { t.Run("IncorrectCode", func(t *testing.T) { nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" body := createBalanceBody(nullifier, "someAntoherCode") - _, respCode := postPatchRequest(t, balancesEndpoint, body, nullifier, false) + _, respCode := postPatchRequest(t, balancesPath(), body, nullifier, false) if respCode != http.StatusNotFound { t.Errorf("want %d got %d", http.StatusNotFound, respCode) } @@ -160,99 +165,298 @@ func TestCreateBalance(t *testing.T) { } func TestVerifyPassport(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" - referrer := "0x0000000000000000000000000000000000000000000000000000000000000001" + t.Run("VerifyPassport", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000010" + createBalance(t, nullifier, genesisCode) - balance := getBalance(t, referrer) - if balance.Data.Attributes.ActiveReferralCodes == nil || - len(*balance.Data.Attributes.ActiveReferralCodes) == 0 { - t.Fatalf("active referral codes for user %s absent", referrer) - } - createBalance(t, nullifier, (*balance.Data.Attributes.ActiveReferralCodes)[0]) + proof := baseProof + proof.PubSignals[zk.Citizenship] = ukrCode + body := verifyPassportBody(nullifier, proof) - proof := baseProof - proof.PubSignals[zk.Citizenship] = ukrCode - body := verifyPassportBody(nullifier, proof) - - t.Run("VerifyPassport", func(t *testing.T) { - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) - if respCode != http.StatusNoContent { - t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) + _, respCode := postPatchRequest(t, verifyPassportPath(nullifier), body, nullifier, false) + if respCode != http.StatusOK { + t.Errorf("failed to verify passport: want %d got %d", http.StatusOK, respCode) } }) - t.Run("VerifyOneMore", func(t *testing.T) { - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) + t.Run("VerifyPassportSecondTime", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000010" + + proof := baseProof + proof.PubSignals[zk.Citizenship] = ukrCode + body := verifyPassportBody(nullifier, proof) + + // depend on previous test, because balance for nullifier must exist + _, respCode := postPatchRequest(t, verifyPassportPath(nullifier), body, nullifier, false) if respCode != http.StatusTooManyRequests { t.Errorf("want %d got %d", http.StatusTooManyRequests, respCode) } }) t.Run("IncorrectCountryCode", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000020" + createBalance(t, nullifier, genesisCode) + + proof := baseProof proof.PubSignals[zk.Citizenship] = "6974819" - body = verifyPassportBody(referrer, proof) - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+referrer+"/verifypassport", body, referrer, false) + body := verifyPassportBody(nullifier, proof) + + _, respCode := postPatchRequest(t, verifyPassportPath(nullifier), body, nullifier, false) if respCode != http.StatusInternalServerError { t.Errorf("want %d got %d", http.StatusInternalServerError, respCode) } }) } -func TestClaimEvent(t *testing.T) { - nullifier1 := "0x0000000000000000000000000000000000000000000000000000000000000010" - nullifier2 := "0x0000000000000000000000000000000000000000000000000000000000000020" +func TestAutoClaimEvent(t *testing.T) { + t.Run("SuccessClaimPassportScan", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000100" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, usaCode) - balance1 := createBalance(t, nullifier1, genesisCode) - if balance1.Data.Attributes.ActiveReferralCodes == nil || - len(*balance1.Data.Attributes.ActiveReferralCodes) == 0 { - t.Fatalf("active referral codes for user %s absent", nullifier1) - } + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) + if eventID == "" { + t.Log("passport scan event absent") + return + } + if eventStatus != string(data.EventClaimed) { + t.Fatalf("want passport scan event status %s got %s", data.EventClaimed, eventStatus) + } + }) - passportScanEventID, _ := getEventFromList(getEvents(t, nullifier1), evtypes.TypePassportScan) - if passportScanEventID == "" { - t.Fatalf("passport scan event absent for %s", nullifier1) - } + t.Run("ReservedDisallowed", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000200" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, gbrCode) - t.Run("TryClaimOpenEvent", func(t *testing.T) { - body := claimEventBody(passportScanEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+passportScanEventID, body, nullifier1, true) - if respCode != http.StatusNotFound { - t.Errorf("want %d got %d", http.StatusNotFound, respCode) + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) + if eventID == "" { + t.Log("passport scan event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want passport scan event status %s got %s", data.EventFulfilled, eventStatus) } }) - createBalance(t, nullifier2, (*balance1.Data.Attributes.ActiveReferralCodes)[0]) - verifyPassport(t, nullifier2, ukrCode) + // this test depend on `SuccessClaimPassportScan` + t.Run("ReserveLimitReached", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000300" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, usaCode) - refSpecEventID, _ := getEventFromList(getEvents(t, nullifier1), evtypes.TypeReferralSpecific) - if refSpecEventID == "" { - t.Fatalf("referral specific event absent for %s", nullifier1) - } + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) + if eventID == "" { + t.Log("passport scan event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want passport scan event status %s got %s", data.EventFulfilled, eventStatus) + } + }) + + t.Run("CountryBanned", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000000400" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, canCode) + + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) + if eventID == "" { + t.Log("passport scan event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want passport scan event status %s got %s", data.EventFulfilled, eventStatus) + } + }) + + t.Run("ReferralSpecific", func(t *testing.T) { + referrer := "0x0000000000000000000000000000000000000000000000000000000000000500" + referrerBalance := createBalance(t, referrer, genesisCode) + verifyPassport(t, referrer, ukrCode) + if referrerBalance.Data.Attributes.ReferralCodes == nil || len(*referrerBalance.Data.Attributes.ReferralCodes) == 0 { + t.Fatal("referrer's referral codes must exists") + } + + if (*referrerBalance.Data.Attributes.ReferralCodes)[0].Status != data.StatusActive { + t.Fatal("first referrer's referral code inactive") + } + + referred := "0x0000000000000000000000000000000000000000000000000000000000000600" + createBalance(t, referred, (*referrerBalance.Data.Attributes.ReferralCodes)[0].Id) + verifyPassport(t, referred, ukrCode) - t.Run("TryClaimEventWithoutPassport", func(t *testing.T) { - body := claimEventBody(refSpecEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+refSpecEventID, body, nullifier1, true) + eventID, eventStatus := getEventFromList(getEvents(t, referrer), evtypes.TypeReferralSpecific) + if eventID == "" { + t.Log("referral specific event absent") + return + } + if eventStatus != string(data.EventClaimed) { + t.Fatalf("want referral specific event status %s got %s", data.EventClaimed, eventStatus) + } + }) + + // User can have a lot unclaimed fulfilled referral specific events if user not scan passport + t.Run("ReferralSpecifics", func(t *testing.T) { + referrer := "0x0000000000000000000000000000000000000000000000000000000000000700" + referrerBalance := createBalance(t, referrer, genesisCode) + if referrerBalance.Data.Attributes.ReferralCodes == nil || len(*referrerBalance.Data.Attributes.ReferralCodes) < 2 { + t.Fatal("referrer's referral codes must exists") + } + + if (*referrerBalance.Data.Attributes.ReferralCodes)[0].Status != data.StatusActive { + t.Fatal("first referrer's referral code inactive") + } + + referred1 := "0x0000000000000000000000000000000000000000000000000000000000000800" + createBalance(t, referred1, (*referrerBalance.Data.Attributes.ReferralCodes)[0].Id) + verifyPassport(t, referred1, ukrCode) + + if (*referrerBalance.Data.Attributes.ReferralCodes)[1].Status != data.StatusActive { + t.Fatal("second referrer's referral code inactive") + } + + referred2 := "0x0000000000000000000000000000000000000000000000000000000000000900" + createBalance(t, referred2, (*referrerBalance.Data.Attributes.ReferralCodes)[1].Id) + verifyPassport(t, referred2, ukrCode) + + fulfilledEventCount := 0 + referrerEvents := getEvents(t, referrer) + for _, event := range referrerEvents.Data { + if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventFulfilled) { + fulfilledEventCount += 1 + } + } + + if fulfilledEventCount != 2 { + t.Fatalf("count of fulfilled events for referrer must be 2") + } + + verifyPassport(t, referrer, ukrCode) + + claimedEventCount := 0 + referrerEvents = getEvents(t, referrer) + for _, event := range referrerEvents.Data { + if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventClaimed) { + claimedEventCount += 1 + } + } + + if claimedEventCount != 2 { + t.Fatalf("count of claimed events for referrer must be 2") + } + }) +} + +func TestClaimEvent(t *testing.T) { + t.Run("WithoutPassport", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000001000" + createBalance(t, nullifier, genesisCode) + + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) + if eventID == "" { + t.Log("free weekly event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) + } + + body := claimEventBody(eventID) + _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) if respCode != http.StatusForbidden { t.Errorf("want %d got %d", http.StatusForbidden, respCode) } }) - passportScanEventID, _ = getEventFromList(getEvents(t, nullifier2), evtypes.TypePassportScan) - if passportScanEventID == "" { - t.Fatalf("passport scan event absent for %s", nullifier2) - } + t.Run("SuccessClaim", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000002000" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, fraCode) + + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) + if eventID == "" { + t.Log("free weekly event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) + } - t.Run("ClaimEvent", func(t *testing.T) { - body := claimEventBody(passportScanEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+passportScanEventID, body, nullifier2, true) + body := claimEventBody(eventID) + _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) if respCode != http.StatusOK { t.Errorf("want %d got %d", http.StatusOK, respCode) } }) + + t.Run("ReserveDisallowed", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000003000" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, indCode) + + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) + if eventID == "" { + t.Log("free weekly event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) + } + + body := claimEventBody(eventID) + _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) + if respCode != http.StatusForbidden { + t.Errorf("want %d got %d", http.StatusForbidden, respCode) + } + }) + + // this test depend on `SuccessClaimPassportScan` + t.Run("ReserveLimitReached", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000004000" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, fraCode) + + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) + if eventID == "" { + t.Log("passport scan event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) + } + + body := claimEventBody(eventID) + _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) + if respCode != http.StatusForbidden { + t.Errorf("want %d got %d", http.StatusForbidden, respCode) + } + }) + + t.Run("CountryBanned", func(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000005000" + createBalance(t, nullifier, genesisCode) + verifyPassport(t, nullifier, mcoCode) + + eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) + if eventID == "" { + t.Log("passport scan event absent") + return + } + if eventStatus != string(data.EventFulfilled) { + t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) + } + + body := claimEventBody(eventID) + _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) + if respCode != http.StatusForbidden { + t.Errorf("want %d got %d", http.StatusForbidden, respCode) + } + }) } func TestLevels(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000100" + nullifier := "0x0000000000000000000000000000000000000000000000000000000000010000" balance := createBalance(t, nullifier, genesisCode) if balance.Data.Attributes.Level != 1 { @@ -261,13 +465,6 @@ func TestLevels(t *testing.T) { verifyPassport(t, nullifier, ukrCode) - passportScanEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if passportScanEventID == "" { - t.Fatalf("passport scan event absent for %s", nullifier) - } - - claimEvent(t, passportScanEventID, nullifier) - balance = getBalance(t, nullifier) if balance.Data.Attributes.Level != 2 { t.Fatalf("balance level must be 2, got %d: %s", balance.Data.Attributes.Level, nullifier) @@ -286,71 +483,17 @@ func TestLevels(t *testing.T) { } // must never panic because of logic getBalance - if len(*balance.Data.Attributes.ActiveReferralCodes) != 15 { - t.Fatalf("balance referral codes must be 15, got %d: %s", len(*balance.Data.Attributes.ActiveReferralCodes), nullifier) + if len(*balance.Data.Attributes.ReferralCodes) != 15 { + t.Fatalf("balance referral codes must be 15, got %d: %s", len(*balance.Data.Attributes.ReferralCodes), nullifier) } } -func TestCountryPools(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000001000" - - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, usaCode) - - t.Run("UnderLimit", func(t *testing.T) { - passportScanEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if passportScanEventID == "" { - t.Fatalf("passport scan event absent for %s", nullifier) - } - - claimEvent(t, passportScanEventID, nullifier) - }) - - t.Run("OverLimit", func(t *testing.T) { - freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if freeWeeklyEventID == "" { - t.Fatalf("free weekly event absent for %s", nullifier) - } - - body := claimEventBody(freeWeeklyEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } - }) - - nullifier = "0x0000000000000000000000000000000000000000000000000000000000002000" - - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, gbrCode) - - t.Run("NotAllowedReserve", func(t *testing.T) { - freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if freeWeeklyEventID == "" { - t.Fatalf("free weekly event absent for %s", nullifier) - } - - body := claimEventBody(freeWeeklyEventID) - _, respCode := postPatchRequest(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } - }) - - nullifier = "0x0000000000000000000000000000000000000000000000000000000000003000" +func TestCountryPoolsDefault(t *testing.T) { + nullifier := "0x0000000000000000000000000000000000000000000000000000000000100000" createBalance(t, nullifier, genesisCode) verifyPassport(t, nullifier, deuCode) - t.Run("DefaultUnderLimit", func(t *testing.T) { - passportScanEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if passportScanEventID == "" { - t.Fatalf("passport scan event absent for %s", nullifier) - } - - claimEvent(t, passportScanEventID, nullifier) - }) - t.Run("DefaultOverLimit", func(t *testing.T) { freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) if freeWeeklyEventID == "" { @@ -378,7 +521,7 @@ func claimEvent(t *testing.T, id, nullifier string) resources.EventResponse { body := claimEventBody(id) respBody, respCode := postPatchRequest(t, eventsEndpoint+"/"+id, body, nullifier, true) if respCode != http.StatusOK { - t.Errorf("want %d got %d", http.StatusOK, respCode) + t.Fatalf("want %d got %d", http.StatusOK, respCode) } var event resources.EventResponse @@ -390,15 +533,23 @@ func claimEvent(t *testing.T, id, nullifier string) resources.EventResponse { return event } -func verifyPassport(t *testing.T, nullifier, country string) { +func verifyPassport(t *testing.T, nullifier, country string) resources.PassportEventStateResponse { proof := baseProof proof.PubSignals[zk.Citizenship] = country body := verifyPassportBody(nullifier, proof) - _, respCode := postPatchRequest(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) - if respCode != http.StatusNoContent { - t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) + respBody, respCode := postPatchRequest(t, verifyPassportPath(nullifier), body, nullifier, false) + if respCode != http.StatusOK { + t.Fatalf("failed to verify passport: want %d got %d", http.StatusOK, respCode) + } + + var resp resources.PassportEventStateResponse + err := json.Unmarshal(respBody, &resp) + if err != nil { + t.Fatalf("failed to unmarshal passport event state response: %v", err) } + + return resp } func getEvents(t *testing.T, nullifier string) resources.EventListResponse { @@ -409,7 +560,7 @@ func getEvents(t *testing.T, nullifier string) resources.EventListResponse { return query }(), nullifier) if respCode != http.StatusOK { - t.Errorf("failed to get events: want %d got %d", http.StatusOK, respCode) + t.Fatalf("failed to get events: want %d got %d", http.StatusOK, respCode) } var events resources.EventListResponse @@ -447,7 +598,7 @@ func getBalance(t *testing.T, nullifier string) resources.BalanceResponse { return query }(), nullifier) if respCode != http.StatusOK { - t.Errorf("failed to get balance: want %d got %d", http.StatusOK, respCode) + t.Fatalf("failed to get balance: want %d got %d", http.StatusOK, respCode) } var balance resources.BalanceResponse @@ -459,7 +610,9 @@ func getBalance(t *testing.T, nullifier string) resources.BalanceResponse { return balance } -func editReferrals(t *testing.T) +// func editReferrals(t *testing.T) { + +// } func verifyPassportBody(nullifier string, proof zkptypes.ZKProof) resources.VerifyPassportRequest { return resources.VerifyPassportRequest{ From ef54d5f603945112ffbb56a2e6f6f4bf3c08d47e Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Wed, 19 Jun 2024 14:23:22 +0300 Subject: [PATCH 08/22] Refactor test for levels logic --- requests_test.go | 338 ++++++++++++++++++++++++++++------------------- 1 file changed, 204 insertions(+), 134 deletions(-) diff --git a/requests_test.go b/requests_test.go index 92102ab..0fb5fc9 100644 --- a/requests_test.go +++ b/requests_test.go @@ -12,6 +12,7 @@ import ( "os" "runtime/debug" "strconv" + "strings" "testing" "time" @@ -19,6 +20,7 @@ import ( "github.com/google/jsonapi" zkptypes "github.com/iden3/go-rapidsnark/types" "github.com/rarimo/rarime-points-svc/internal/config" + "github.com/rarimo/rarime-points-svc/internal/data" "github.com/rarimo/rarime-points-svc/internal/data/evtypes" "github.com/rarimo/rarime-points-svc/internal/service/requests" "github.com/rarimo/rarime-points-svc/resources" @@ -29,8 +31,9 @@ import ( ) const ( - requestTimeout = time.Second // use bigger on debug with breakpoints to prevent fails - defaultConfigFile = "config.local.yaml" + requestTimeout = time.Second // use bigger on debug with breakpoints to prevent fails + defaultConfigFile = "config-testing.yaml" // run service with this config for consistency with tests + defaultReferralsCount = 5 ukrCode = "5589842" usaCode = "5591873" @@ -73,7 +76,7 @@ func TestMain(m *testing.M) { setUp() exitVal = m.Run() - tearDown() + tearDown() // it is DB cleanup, but maybe it is easier to do with migrate down or random nullifiers? } func setUp() { @@ -87,7 +90,7 @@ func setUp() { globalCfg = config.New(kv.MustFromEnv()) apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1", globalCfg.Listener().Addr().String()) - refs, err := editReferrals(genesisBalance, 20) + refs, err := editReferrals(genesisBalance, 100) if err != nil { panic(fmt.Errorf("failed to edit referrals: %w", err)) } @@ -106,35 +109,14 @@ func TestCreateBalance(t *testing.T) { otRefCode string ) - validBalanceChecks := func(t *testing.T, nullifier, code string) { - resp, err := createBalance(nullifier, genesisCode) - require.NoError(t, err) - require.Equal(t, nullifier, resp.Data.ID) - - attr := resp.Data.Attributes - - require.NotNil(t, attr.IsDisabled) - require.NotNil(t, attr.IsVerified) - require.NotNil(t, attr.ReferralCodes) - require.NotEmpty(t, *attr.ReferralCodes) - - assert.Equal(t, 0, attr.Amount) - assert.False(t, *attr.IsDisabled) - assert.False(t, *attr.IsVerified) - assert.Equal(t, 1, attr.Level) - assert.NotNil(t, attr.Rank) - - otRefCode = (*attr.ReferralCodes)[0].Id - require.NotEmpty(t, otRefCode) - } - // fixme @violog: looks like fail on assert/require won't stop outer tests, must check before proceeding t.Run("BalanceGenesisCode", func(t *testing.T) { - validBalanceChecks(t, nullifierShared, genesisCode) + resp := createAndValidateBalance(t, nullifierShared, genesisCode) + otRefCode = (*resp.Data.Attributes.ReferralCodes)[0].Id }) t.Run("BalanceOneTimeCode", func(t *testing.T) { - validBalanceChecks(t, nextN(), otRefCode) + createAndValidateBalance(t, nextN(), otRefCode) }) t.Run("SameBalanceConflict", func(t *testing.T) { @@ -169,45 +151,112 @@ func TestCreateBalance(t *testing.T) { }) } -func TestVerifyPassport(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000002" - referrer := "0x0000000000000000000000000000000000000000000000000000000000000001" +func createAndValidateBalance(t *testing.T, nullifier, code string) resources.BalanceResponse { + t.Helper() - balance := getBalance(t, referrer) - if balance.Data.Attributes.ActiveReferralCodes == nil || - len(*balance.Data.Attributes.ActiveReferralCodes) == 0 { - t.Fatalf("active referral codes for user %s absent", referrer) - } - createBalance(t, nullifier, (*balance.Data.Attributes.ActiveReferralCodes)[0]) + resp, err := createBalance(nullifier, code) + require.NoError(t, err) + require.Equal(t, nullifier, resp.Data.ID) - proof := baseProof - proof.PubSignals[zk.Citizenship] = ukrCode - body := verifyPassportBody(nullifier, proof) + attr := resp.Data.Attributes - t.Run("VerifyPassport", func(t *testing.T) { - _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) - if respCode != http.StatusNoContent { - t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) + require.NotNil(t, attr.IsDisabled) + require.NotNil(t, attr.IsVerified) + require.NotNil(t, attr.ReferralCodes) + require.NotEmpty(t, *attr.ReferralCodes) + + assert.Equal(t, 0, attr.Amount) + assert.False(t, *attr.IsDisabled) + assert.False(t, *attr.IsVerified) + assert.Equal(t, 1, attr.Level) + assert.NotNil(t, attr.Rank) + + rc := (*attr.ReferralCodes)[0] + assert.NotEmpty(t, rc.Id) + assert.Equal(t, data.StatusActive, rc.Status) + return resp +} + +func TestVerifyPassport(t *testing.T) { + var ( + referrer = nextN() + referee = nextN() + balance1 = createAndValidateBalance(t, referrer, genesisCode) + ) + createAndValidateBalance(t, referee, (*balance1.Data.Attributes.ReferralCodes)[0].Id) + + var countriesResp resources.CountriesConfigResponse + err := getRequest("public/countries", nil, "", &countriesResp) + require.NoError(t, err) + + countriesList := countriesResp.Data.Attributes.Countries + require.Contains(t, countriesList, ukrCode) + require.Contains(t, countriesList, usaCode) + + // ensure the same behaviour whitelisted and banned countries + for _, c := range countriesList { + if c.Code == ukrCode { + require.True(t, c.ReserveAllowed) + require.True(t, c.WithdrawalAllowed) + continue } + if c.Code == usaCode { + require.False(t, c.ReserveAllowed) + require.False(t, c.WithdrawalAllowed) + } + } + + // passport verification should lead to referral event appearance and claimed passport event + t.Run("VerifyPassport", func(t *testing.T) { + resp, err := verifyPassport(referee, ukrCode) + require.NoError(t, err) + assert.True(t, resp.Data.Attributes.Claimed) + getAndValidateBalance(t, referee, true) }) - t.Run("VerifyOneMore", func(t *testing.T) { - _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) - if respCode != http.StatusTooManyRequests { - t.Errorf("want %d got %d", http.StatusTooManyRequests, respCode) - } + t.Run("VerifyPassportSecondTime", func(t *testing.T) { + _, err = verifyPassport(referee, ukrCode) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, "429", apiErr.Status) + getAndValidateBalance(t, referee, true) }) t.Run("IncorrectCountryCode", func(t *testing.T) { - proof.PubSignals[zk.Citizenship] = "6974819" - body = verifyPassportBody(referrer, proof) - _, respCode := requestWithBody(t, balancesEndpoint+"/"+referrer+"/verifypassport", body, referrer, false) - if respCode != http.StatusInternalServerError { - t.Errorf("want %d got %d", http.StatusInternalServerError, respCode) - } + n := nextN() + _, err = verifyPassport(n, "6974819") + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + assert.Equal(t, "429", apiErr.Status) + getAndValidateBalance(t, n, false) + }) + + t.Run("BlacklistedCountry", func(t *testing.T) { + n := nextN() + resp, err := verifyPassport(n, usaCode) + require.NoError(t, err) + assert.True(t, resp.Data.Attributes.Claimed) + getAndValidateBalance(t, n, true) }) } +func getAndValidateBalance(t *testing.T, nullifier string, isVerified bool) resources.BalanceResponse { + resp, err := getBalance(nullifier) + require.NoError(t, err) + + attr := resp.Data.Attributes + require.NotNil(t, attr.IsDisabled) + require.NotNil(t, attr.IsVerified) + assert.False(t, *attr.IsDisabled) + assert.Equal(t, isVerified, *attr.IsVerified) + + assert.NotNil(t, attr.Rank) + assert.NotNil(t, attr.ReferralCodes) + assert.NotEmpty(t, *attr.ReferralCodes) + + return resp +} + func TestClaimEvent(t *testing.T) { nullifier1 := "0x0000000000000000000000000000000000000000000000000000000000000010" nullifier2 := "0x0000000000000000000000000000000000000000000000000000000000000020" @@ -262,43 +311,90 @@ func TestClaimEvent(t *testing.T) { } func TestLevels(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000100" - - balance := createBalance(t, nullifier, genesisCode) - if balance.Data.Attributes.Level != 1 { - t.Fatalf("balance level must be 1, got %d: %s", balance.Data.Attributes.Level, nullifier) - } - - verifyPassport(t, nullifier, ukrCode) + var ( + nullifier = nextN() - passportScanEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if passportScanEventID == "" { - t.Fatalf("passport scan event absent for %s", nullifier) - } + evTypePassport = globalCfg.EventTypes().Get(evtypes.TypePassportScan) + evTypeWeekly = globalCfg.EventTypes().Get(evtypes.TypeFreeWeekly) - claimEvent(t, passportScanEventID, nullifier) + lvl1Cfg = globalCfg.Levels()[1] + lvl2Cfg = globalCfg.Levels()[2] + lvl3Cfg = globalCfg.Levels()[3] - balance = getBalance(t, nullifier) - if balance.Data.Attributes.Level != 2 { - t.Fatalf("balance level must be 2, got %d: %s", balance.Data.Attributes.Level, nullifier) - } + amountClaim1 = evTypePassport.Reward + amountClaim2 = evTypePassport.Reward + evTypeWeekly.Reward + lvl2Referrals = lvl1Cfg.Referrals + lvl2Cfg.Referrals + lvl3Referrals = lvl2Referrals + lvl3Cfg.Referrals + ) + require.NotNil(t, evTypePassport) + require.NotNil(t, evTypeWeekly) + // ensure that levels are set + require.Equal(t, 1, lvl1Cfg.Level) + require.Equal(t, 2, lvl2Cfg.Level) + require.Equal(t, 3, lvl3Cfg.Level) + // rewards must be equal to level threshold in order to upgrade level for each of 2 claimed events + require.Equal(t, amountClaim1, lvl2Cfg.Threshold) + require.Equal(t, amountClaim2, lvl3Cfg.Threshold) + require.False(t, evTypeWeekly.AutoClaim) + + createAndValidateBalance(t, nullifier, genesisCode) + passportResp, err := verifyPassport(nullifier, ukrCode) + require.NoError(t, err) + assert.True(t, passportResp.Data.Attributes.Claimed) + + status := data.EventClaimed + if !evTypePassport.AutoClaim { + status = data.EventFulfilled + } + eventID := getAndValidateSingleEvent(t, nullifier, evtypes.TypePassportScan, status) + + if !evTypePassport.AutoClaim { + claimEventAndValidate(t, eventID, nullifier, amountClaim1) + } + + balance := getAndValidateBalance(t, nullifier, true) + balanceAttr := balance.Data.Attributes + assert.Equal(t, 2, balanceAttr.Level) + assert.Equal(t, amountClaim1, balanceAttr.Amount) + + refCodes := balanceAttr.ReferralCodes + require.NotNil(t, refCodes) + assert.Equal(t, lvl2Referrals, len(*refCodes)) + + eventID = getAndValidateSingleEvent(t, nullifier, evtypes.TypeFreeWeekly, data.EventFulfilled) + claimEventAndValidate(t, eventID, nullifier, amountClaim2) + + balance = getAndValidateBalance(t, nullifier, true) + balanceAttr = balance.Data.Attributes + assert.Equal(t, 3, balanceAttr.Level) + assert.Equal(t, amountClaim2, balanceAttr.Amount) + + refCodes = balanceAttr.ReferralCodes + require.NotNil(t, refCodes) + assert.Equal(t, lvl3Referrals, len(*refCodes)) +} - freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if freeWeeklyEventID == "" { - t.Fatalf("free weekly event absent for %s", nullifier) - } +func getAndValidateSingleEvent(t *testing.T, nullifier, evType string, status data.EventStatus) string { + resp, err := getEvents(nullifier, evtypes.TypePassportScan) + require.NoError(t, err) + require.Len(t, resp.Data, 1) - claimEvent(t, freeWeeklyEventID, nullifier) + event := resp.Data[0] + attr := event.Attributes - balance = getBalance(t, nullifier) - if balance.Data.Attributes.Level != 3 { - t.Fatalf("balance level must be 3, got %d: %s", balance.Data.Attributes.Level, nullifier) - } + require.NotEmpty(t, event.ID) + assert.Equal(t, evType, attr.Meta.Static.Name) + assert.Equal(t, status, attr.Status) + return event.ID +} - // must never panic because of logic getBalance - if len(*balance.Data.Attributes.ActiveReferralCodes) != 15 { - t.Fatalf("balance referral codes must be 15, got %d: %s", len(*balance.Data.Attributes.ActiveReferralCodes), nullifier) - } +func claimEventAndValidate(t *testing.T, id, nullifier string, reward int64) { + resp, err := claimEvent(id, nullifier) + require.NoError(t, err) + attr := resp.Data.Attributes + assert.Equal(t, data.EventClaimed, attr.Status) + require.NotNil(t, attr.PointsAmount) + assert.Equal(t, reward, *attr.PointsAmount) } func TestCountryPools(t *testing.T) { @@ -375,60 +471,22 @@ func TestCountryPools(t *testing.T) { }) } -func getEventFromList(events resources.EventListResponse, evtype string) (id, status string) { - for _, event := range events.Data { - if event.Attributes.Meta.Static.Name == evtype { - return event.ID, event.Attributes.Status - } - } - return "", "" -} - -func claimEvent(t *testing.T, id, nullifier string) resources.EventResponse { +func claimEvent(id, nullifier string) (resp resources.EventResponse, err error) { body := claimEventBody(id) - respBody, respCode := requestWithBody(t, eventsEndpoint+"/"+id, body, nullifier, true) - if respCode != http.StatusOK { - t.Errorf("want %d got %d", http.StatusOK, respCode) - } - - var event resources.EventResponse - err := json.Unmarshal(respBody, &event) - if err != nil { - t.Fatalf("failed to unmarhal event response: %v", err) - } - - return event -} - -func verifyPassport(t *testing.T, nullifier, country string) { - proof := baseProof - proof.PubSignals[zk.Citizenship] = country - body := verifyPassportBody(nullifier, proof) - - _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) - if respCode != http.StatusNoContent { - t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) - } + err = requestWithBody(eventsEndpoint+"/"+id, "PATCH", nullifier, body, &resp) + return } -func getEvents(t *testing.T, nullifier string) resources.EventListResponse { - respBody, respCode := getRequest(t, - eventsEndpoint, func() url.Values { - query := url.Values{} - query.Add("filter[nullifier]", nullifier) - return query - }(), nullifier) - if respCode != http.StatusOK { - t.Errorf("failed to get events: want %d got %d", http.StatusOK, respCode) - } - - var events resources.EventListResponse - err := json.Unmarshal(respBody, &events) - if err != nil { - t.Fatalf("failed to unmarhal event list response: %v", err) +func getEvents(nullifier string, types ...string) (resp resources.EventListResponse, err error) { + query := url.Values{} + query.Add("filter[nullifier]", nullifier) + query.Add("page[limit]", "100") + if len(types) > 0 { + query.Add("filter[meta.static.name]", strings.Join(types, ",")) } - return events + err = getRequest(eventsEndpoint, query, nullifier, &resp) + return } func createBalance(nullifier, code string) (resp resources.BalanceResponse, err error) { @@ -446,6 +504,14 @@ func getBalance(nullifier string) (resp resources.BalanceResponse, err error) { return } +func verifyPassport(nullifier, country string) (resp resources.PassportEventStateResponse, err error) { + proof := baseProof + proof.PubSignals[zk.Citizenship] = country + body := verifyPassportBody(nullifier, proof) + err = requestWithBody(balancesEndpoint, "POST", nullifier, body, &resp) + return +} + type editReferralsResponse struct { Ref string `json:"referral"` UsageLeft uint64 `json:"usage_left"` @@ -542,6 +608,10 @@ func doRequest(req *http.Request, user string, result any) error { return &jsonapi.ErrorObject{Status: strconv.Itoa(resp.StatusCode)} } + if result == nil { + return nil + } + respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read resp body: %w", err) From 0d124927b0abb8c9f1e6ec4260e040d4297c5156 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Wed, 19 Jun 2024 15:41:34 +0300 Subject: [PATCH 09/22] Claim tests refactoring --- requests_test.go | 414 +++++++++++++++++++++-------------------------- 1 file changed, 182 insertions(+), 232 deletions(-) diff --git a/requests_test.go b/requests_test.go index 5bccdb3..6b72a7e 100644 --- a/requests_test.go +++ b/requests_test.go @@ -81,6 +81,9 @@ func TestMain(m *testing.M) { tearDown() } +func tearDown() { +} + func setUp() { if os.Getenv(kv.EnvViperConfigFile) == "" { err := os.Setenv(kv.EnvViperConfigFile, defaultConfigFile) @@ -112,7 +115,7 @@ func TestCreateBalance(t *testing.T) { ) validBalanceChecks := func(t *testing.T, nullifier, code string) { - resp, err := createBalance(nullifier, genesisCode) + resp, err := createBalance(nullifier, code) require.NoError(t, err) require.Equal(t, nullifier, resp.Data.ID) @@ -182,11 +185,12 @@ func TestVerifyPassport(t *testing.T) { proof := baseProof proof.PubSignals[zk.Citizenship] = ukrCode body := verifyPassportBody(nullifier, proof) - + }) t.Run("VerifyPassport", func(t *testing.T) { _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) if respCode != http.StatusNoContent { t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) + } _, respCode := postPatchRequest(t, verifyPassportPath(nullifier), body, nullifier, false) if respCode != http.StatusOK { t.Errorf("failed to verify passport: want %d got %d", http.StatusOK, respCode) @@ -222,250 +226,227 @@ func TestVerifyPassport(t *testing.T) { }) } -func TestAutoClaimEvent(t *testing.T) { - t.Run("SuccessClaimPassportScan", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000100" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, usaCode) +func TestEventsAutoClaim(t *testing.T) { + t.Run("PassportScanAutoclaim", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if eventID == "" { - t.Log("passport scan event absent") - return - } - if eventStatus != string(data.EventClaimed) { - t.Fatalf("want passport scan event status %s got %s", data.EventClaimed, eventStatus) - } + respVerifyStatus, err := verifyPassport(n, usaCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) + + respBalance, err := getBalance(n) + require.NoError(t, err) + require.Equal(t, 2, respBalance.Data.Attributes.Level) + require.Equal(t, 5, respBalance.Data.Attributes.Amount) + require.NotNil(t, respBalance.Data.Attributes.ReferralCodes) + require.Equal(t, 10, len(*respBalance.Data.Attributes.ReferralCodes)) }) - t.Run("ReservedDisallowed", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000200" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, gbrCode) + // this test depend on previous test + t.Run("PassportScanLimitReached", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if eventID == "" { - t.Log("passport scan event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want passport scan event status %s got %s", data.EventFulfilled, eventStatus) - } + respVerifyStatus, err := verifyPassport(n, usaCode) + require.NoError(t, err) + require.False(t, respVerifyStatus.Data.Attributes.Claimed) }) - // this test depend on `SuccessClaimPassportScan` - t.Run("ReserveLimitReached", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000300" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, usaCode) + t.Run("PassportScanReserveDisallowed", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if eventID == "" { - t.Log("passport scan event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want passport scan event status %s got %s", data.EventFulfilled, eventStatus) - } + respVerifyStatus, err := verifyPassport(n, gbrCode) + require.NoError(t, err) + require.False(t, respVerifyStatus.Data.Attributes.Claimed) }) - t.Run("CountryBanned", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000000400" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, canCode) + t.Run("PassportScanCountryBanned", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypePassportScan) - if eventID == "" { - t.Log("passport scan event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want passport scan event status %s got %s", data.EventFulfilled, eventStatus) - } + respVerifyStatus, err := verifyPassport(n, canCode) + require.NoError(t, err) + require.False(t, respVerifyStatus.Data.Attributes.Claimed) }) - t.Run("ReferralSpecific", func(t *testing.T) { - referrer := "0x0000000000000000000000000000000000000000000000000000000000000500" - referrerBalance := createBalance(t, referrer, genesisCode) - verifyPassport(t, referrer, ukrCode) - if referrerBalance.Data.Attributes.ReferralCodes == nil || len(*referrerBalance.Data.Attributes.ReferralCodes) == 0 { - t.Fatal("referrer's referral codes must exists") - } + t.Run("ReferralSpecificAutoclaim", func(t *testing.T) { + n1, n2 := nextN(), nextN() + respBalance, err := createBalance(n1, genesisCode) + require.NoError(t, err) + require.NotNil(t, respBalance.Data.Attributes.ReferralCodes) + require.NotEmpty(t, (*respBalance.Data.Attributes.ReferralCodes)) - if (*referrerBalance.Data.Attributes.ReferralCodes)[0].Status != data.StatusActive { - t.Fatal("first referrer's referral code inactive") - } + respVerifyStatus, err := verifyPassport(n1, ukrCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) - referred := "0x0000000000000000000000000000000000000000000000000000000000000600" - createBalance(t, referred, (*referrerBalance.Data.Attributes.ReferralCodes)[0].Id) - verifyPassport(t, referred, ukrCode) + respBalance, err = createBalance(n2, (*respBalance.Data.Attributes.ReferralCodes)[0].Id) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, referrer), evtypes.TypeReferralSpecific) - if eventID == "" { - t.Log("referral specific event absent") - return - } - if eventStatus != string(data.EventClaimed) { - t.Fatalf("want referral specific event status %s got %s", data.EventClaimed, eventStatus) - } + _, err = verifyPassport(n2, ukrCode) + require.NoError(t, err) + + respEvents, err := getEvents(n1) + require.NoError(t, err) + _, status := getEventFromList(respEvents, evtypes.TypeReferralSpecific) + require.Equal(t, data.EventClaimed, status) }) // User can have a lot unclaimed fulfilled referral specific events if user not scan passport - t.Run("ReferralSpecifics", func(t *testing.T) { - referrer := "0x0000000000000000000000000000000000000000000000000000000000000700" - referrerBalance := createBalance(t, referrer, genesisCode) - if referrerBalance.Data.Attributes.ReferralCodes == nil || len(*referrerBalance.Data.Attributes.ReferralCodes) < 2 { - t.Fatal("referrer's referral codes must exists") - } - - if (*referrerBalance.Data.Attributes.ReferralCodes)[0].Status != data.StatusActive { - t.Fatal("first referrer's referral code inactive") - } - - referred1 := "0x0000000000000000000000000000000000000000000000000000000000000800" - createBalance(t, referred1, (*referrerBalance.Data.Attributes.ReferralCodes)[0].Id) - verifyPassport(t, referred1, ukrCode) + t.Run("ReferralSpecificsAutoclaim", func(t *testing.T) { + n1, n2, n3 := nextN(), nextN(), nextN() + respBalance, err := createBalance(n1, genesisCode) + require.NoError(t, err) + require.NotNil(t, respBalance.Data.Attributes.ReferralCodes) + require.GreaterOrEqual(t, (*respBalance.Data.Attributes.ReferralCodes), 2) - if (*referrerBalance.Data.Attributes.ReferralCodes)[1].Status != data.StatusActive { - t.Fatal("second referrer's referral code inactive") - } + respBalance, err = createBalance(n2, (*respBalance.Data.Attributes.ReferralCodes)[0].Id) + require.NoError(t, err) + _, err = verifyPassport(n2, ukrCode) + require.NoError(t, err) - referred2 := "0x0000000000000000000000000000000000000000000000000000000000000900" - createBalance(t, referred2, (*referrerBalance.Data.Attributes.ReferralCodes)[1].Id) - verifyPassport(t, referred2, ukrCode) + respBalance, err = createBalance(n3, (*respBalance.Data.Attributes.ReferralCodes)[1].Id) + require.NoError(t, err) + _, err = verifyPassport(n3, ukrCode) + require.NoError(t, err) + respEvents, err := getEvents(n1) + require.NoError(t, err) fulfilledEventCount := 0 - referrerEvents := getEvents(t, referrer) - for _, event := range referrerEvents.Data { + for _, event := range respEvents.Data { if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventFulfilled) { fulfilledEventCount += 1 } } + require.Equal(t, 2, fulfilledEventCount) - if fulfilledEventCount != 2 { - t.Fatalf("count of fulfilled events for referrer must be 2") - } - - verifyPassport(t, referrer, ukrCode) + respVerifyStatus, err := verifyPassport(n1, ukrCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) + respEvents, err = getEvents(n1) + require.NoError(t, err) claimedEventCount := 0 - referrerEvents = getEvents(t, referrer) - for _, event := range referrerEvents.Data { - if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventClaimed) { + for _, event := range respEvents.Data { + if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventFulfilled) { claimedEventCount += 1 } } - - if claimedEventCount != 2 { - t.Fatalf("count of claimed events for referrer must be 2") - } + require.Equal(t, 2, claimedEventCount) }) } func TestClaimEvent(t *testing.T) { t.Run("WithoutPassport", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000001000" - createBalance(t, nullifier, genesisCode) + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if eventID == "" { - t.Log("free weekly event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) - } + respEvents, err := getEvents(n) + require.NoError(t, err) + eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) + require.Equal(t, data.EventFulfilled, status) - body := claimEventBody(eventID) - _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } + _, err = claimEvent(eventID, n) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "403", apiErr.Status) }) - t.Run("SuccessClaim", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000002000" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, fraCode) + t.Run("IncorrectEventID", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if eventID == "" { - t.Log("free weekly event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) - } + _, err = verifyPassport(n, ukrCode) + require.NoError(t, err) - body := claimEventBody(eventID) - _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) - if respCode != http.StatusOK { - t.Errorf("want %d got %d", http.StatusOK, respCode) - } + _, err = claimEvent("event", n) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "404", apiErr.Status) }) - t.Run("ReserveDisallowed", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000003000" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, indCode) + t.Run("EventClaim", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if eventID == "" { - t.Log("free weekly event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) - } + _, err = verifyPassport(n, fraCode) + require.NoError(t, err) - body := claimEventBody(eventID) - _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } + respEvents, err := getEvents(n) + require.NoError(t, err) + eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) + require.Equal(t, data.EventFulfilled, status) + + respEvent, err := claimEvent(eventID, n) + require.NoError(t, err) + require.Equal(t, data.EventClaimed, respEvent.Data.Attributes.Status) }) - // this test depend on `SuccessClaimPassportScan` + // this test depend on previous test t.Run("ReserveLimitReached", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000004000" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, fraCode) + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if eventID == "" { - t.Log("passport scan event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) - } + _, err = verifyPassport(n, fraCode) + require.NoError(t, err) - body := claimEventBody(eventID) - _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } + respEvents, err := getEvents(n) + require.NoError(t, err) + eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) + require.Equal(t, data.EventFulfilled, status) + + _, err = claimEvent(eventID, n) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "403", apiErr.Status) + }) + + t.Run("ReserveDisallowed", func(t *testing.T) { + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) + + _, err = verifyPassport(n, indCode) + require.NoError(t, err) + + respEvents, err := getEvents(n) + require.NoError(t, err) + eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) + require.Equal(t, data.EventFulfilled, status) + + _, err = claimEvent(eventID, n) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "403", apiErr.Status) }) t.Run("CountryBanned", func(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000005000" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, mcoCode) + n := nextN() + _, err := createBalance(n, genesisCode) + require.NoError(t, err) - eventID, eventStatus := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if eventID == "" { - t.Log("passport scan event absent") - return - } - if eventStatus != string(data.EventFulfilled) { - t.Fatalf("want free weekly event status %s got %s", data.EventFulfilled, eventStatus) - } + _, err = verifyPassport(n, mcoCode) + require.NoError(t, err) - body := claimEventBody(eventID) - _, respCode := postPatchRequest(t, eventsSpecificPath(eventID), body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } + respEvents, err := getEvents(n) + require.NoError(t, err) + eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) + require.Equal(t, data.EventFulfilled, status) + + _, err = claimEvent(eventID, n) + var apiErr jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "403", apiErr.Status) }) } @@ -531,59 +512,28 @@ func getEventFromList(events resources.EventListResponse, evtype string) (id, st return "", "" } -func claimEvent(t *testing.T, id, nullifier string) resources.EventResponse { +func claimEvent(id, nullifier string) (resp resources.EventResponse, err error) { body := claimEventBody(id) - respBody, respCode := requestWithBody(t, eventsEndpoint+"/"+id, body, nullifier, true) - if respCode != http.StatusOK { - t.Fatalf("want %d got %d", http.StatusOK, respCode) - } - - var event resources.EventResponse - err := json.Unmarshal(respBody, &event) - if err != nil { - t.Fatalf("failed to unmarhal event response: %v", err) - } - - return event + err = requestWithBody(eventsEndpoint+"/"+id, "PATCH", nullifier, body, &resp) + return } -func verifyPassport(t *testing.T, nullifier, country string) resources.PassportEventStateResponse { +func verifyPassport(nullifier, country string) (resp resources.PassportEventStateResponse, err error) { proof := baseProof proof.PubSignals[zk.Citizenship] = country body := verifyPassportBody(nullifier, proof) - respBody, respCode := postPatchRequest(t, verifyPassportPath(nullifier), body, nullifier, false) - if respCode != http.StatusOK { - t.Fatalf("failed to verify passport: want %d got %d", http.StatusOK, respCode) - } - - var resp resources.PassportEventStateResponse - err := json.Unmarshal(respBody, &resp) - if err != nil { - t.Fatalf("failed to unmarshal passport event state response: %v", err) - } - - return resp + err = requestWithBody(balancesEndpoint+"/"+nullifier+"/verifypassport", "POST", nullifier, body, &resp) + return } -func getEvents(t *testing.T, nullifier string) resources.EventListResponse { - respBody, respCode := getRequest(t, - eventsEndpoint, func() url.Values { - query := url.Values{} - query.Add("filter[nullifier]", nullifier) - return query - }(), nullifier) - if respCode != http.StatusOK { - t.Fatalf("failed to get events: want %d got %d", http.StatusOK, respCode) - } - - var events resources.EventListResponse - err := json.Unmarshal(respBody, &events) - if err != nil { - t.Fatalf("failed to unmarhal event list response: %v", err) - } +func getEvents(nullifier string) (resp resources.EventListResponse, err error) { + query := url.Values{} + query.Add("filter[nullifier]", nullifier) + query.Add("filter[status]", "claimed,fulfilled,open") - return events + err = getRequest(eventsEndpoint, query, nullifier, &resp) + return } func createBalance(nullifier, code string) (resp resources.BalanceResponse, err error) { From def6c9c6a5a300e4285031faae3db2522f8702af Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Wed, 19 Jun 2024 15:53:09 +0300 Subject: [PATCH 10/22] Add testing config, check for balances and genesis code on setup --- config-testing.yaml | 105 ++++++++++++++++++++++++++++++++++++++++++++ config.yaml | 4 +- requests_test.go | 37 +++++++++++++--- 3 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 config-testing.yaml diff --git a/config-testing.yaml b/config-testing.yaml new file mode 100644 index 0000000..fdfcfe2 --- /dev/null +++ b/config-testing.yaml @@ -0,0 +1,105 @@ +log: + level: debug + disable_sentry: true + +db: + url: postgres://points:points@localhost:5432/points?sslmode=disable + +listener: + addr: localhost:8000 + +event_types: + types: + - name: free_weekly + reward: 5 + frequency: weekly + title: Free weekly points + short_description: Get free points every week, just pressing the button + description: "## Free Weekly Points\n\nThis is a weekly event where users can earn free points.\n\n### How it works\n\n- Users are eligible to participate once every week.\n- Upon participation, users will receive 100 points.\n- These points can be used for various features in the application.\n\nParticipate every week and maximize your rewards!\n" + logo: https://pbs.twimg.com/profile_images/1639021161257263105/XmT0EBnK_400x400.jpg + starts_at: 2024-03-23T12:42:00Z + - name: passport_scan + reward: 10 + frequency: one-time + title: Passport verification + short_description: Scan your passport to unlock features and get points + description: "## Passport verification\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" + logo: https://pbs.twimg.com/profile_images/1639021161257263105/XmT0EBnK_400x400.jpg + starts_at: 2024-03-23T12:42:00Z + - name: referral_common + reward: 15 + frequency: one-time + title: Refer new users + short_description: Refer friends and get a reward for each friend who verifies the passport + description: "## Referral program\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" + action_url: https://rarimo.com + - name: referral_specific + reward: 3 + frequency: unlimited + no_auto_open: true + title: Refer user + short_description: The user has verified the passport. Claim the reward! + description: "## Referral program\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" + action_url: https://rarimo.com + - name: new_limited_event + title: Limited event + reward: 5 + frequency: one-time + short_description: This event will expire soon + description: "## Limited event\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" + action_url: https://rarimo.com + expires_at: 2024-11-01T00:00:00Z + - name: new_expired_event + title: Expired event + reward: 3 + frequency: one-time + short_description: This event has expired + description: "## Expired event\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" + action_url: https://rarimo.com + expires_at: 2024-06-10T00:00:00Z + +levels: + levels: + - lvl: 1 + threshold: 0 + referrals: 5 + withdrawal_allowed: false + - lvl: 2 + threshold: 30 + referrals: 5 + withdrawal_allowed: true + - lvl: 3 + threshold: 50 + referrals: 5 + withdrawal_allowed: true + +countries: + countries: + - code: "USA" + reserve_limit: 0 + reserve_allowed: false + withdrawal_allowed: false + - code: "default" + reserve_limit: 100000 + reserve_allowed: true + withdrawal_allowed: true + +auth: + addr: http://localhost:9030 + +broadcaster: + addr: localhost:9010 + sender_account: "rarimo1ghcxdrgmy8duq8cu68fgmlp2sfmfwkh2dl4chl" + +verifier: + verification_key_path: "./verification_key.json" + allowed_age: 18 + allowed_identity_timestamp: 1715698750 + +root_verifier: + rpc: https://your-rpc + contract: 0x7DdAde70efADf832A600ba483FAD26fCA477FE2A + request_timeout: 10s + +withdrawal: + point_price_urmo: 1000000 # 1 RMO diff --git a/config.yaml b/config.yaml index 818a968..a7cacfe 100644 --- a/config.yaml +++ b/config.yaml @@ -68,8 +68,8 @@ countries: withdrawal_allowed: true - code: "USA" reserve_limit: 5 - reserve_allowed: true - withdrawal_allowed: true + reserve_allowed: false + withdrawal_allowed: false - code: "GBR" reserve_limit: 5 reserve_allowed: false diff --git a/requests_test.go b/requests_test.go index 0fb5fc9..5c16bfb 100644 --- a/requests_test.go +++ b/requests_test.go @@ -22,6 +22,7 @@ import ( "github.com/rarimo/rarime-points-svc/internal/config" "github.com/rarimo/rarime-points-svc/internal/data" "github.com/rarimo/rarime-points-svc/internal/data/evtypes" + "github.com/rarimo/rarime-points-svc/internal/data/pg" "github.com/rarimo/rarime-points-svc/internal/service/requests" "github.com/rarimo/rarime-points-svc/resources" zk "github.com/rarimo/zkverifier-kit" @@ -76,7 +77,6 @@ func TestMain(m *testing.M) { setUp() exitVal = m.Run() - tearDown() // it is DB cleanup, but maybe it is easier to do with migrate down or random nullifiers? } func setUp() { @@ -89,20 +89,47 @@ func setUp() { globalCfg = config.New(kv.MustFromEnv()) apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1", globalCfg.Listener().Addr().String()) + initGenesisRef() - refs, err := editReferrals(genesisBalance, 100) + // let's not introduce counting function just for test + balances, err := pg.NewBalances(globalCfg.DB()).Select() if err != nil { - panic(fmt.Errorf("failed to edit referrals: %w", err)) + panic(fmt.Errorf("failed to select balances: %w", err)) } - genesisCode = refs.Ref + // to prevent repeating cleanups, more balances are created + currentNullifierIndex = len(balances) nullifiers = make([]string, 20) for i := range nullifiers { - hash := sha256.Sum256([]byte{byte(i)}) + hash := sha256.Sum256([]byte{byte(i + len(balances))}) nullifiers[i] = hexutil.Encode(hash[:]) } } +func initGenesisRef() { + gen, err := pg.NewReferrals(globalCfg.DB()). + FilterConsumed(). + FilterByNullifier(genesisBalance). + Select() + if err != nil { + panic(fmt.Errorf("failed to get genesis balance: %w", err)) + } + if len(gen) > 1 { + panic(fmt.Errorf("%d genesis referral codes found", len(gen))) + } + + if len(gen) == 0 || gen[0].UsageLeft < 20 { // approximate amount to run tests + refs, err := editReferrals(genesisBalance, 10000) + if err != nil { + panic(fmt.Errorf("failed to edit referrals: %w", err)) + } + genesisCode = refs.Ref + return + } + + genesisCode = gen[0].ID +} + func TestCreateBalance(t *testing.T) { var ( nullifierShared = nextN() From 31501b3717eb92f5adaddd420741ed071741b60c Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Wed, 19 Jun 2024 14:23:57 +0300 Subject: [PATCH 11/22] Now init level for balance it is 0. Fix bug with adding fulfilled events for referrer without scanned passport --- internal/service/handlers/claim_event.go | 31 ++++++++---- internal/service/handlers/create_balance.go | 53 +++++++++++++------- internal/service/handlers/verify_passport.go | 31 ++++++------ 3 files changed, 73 insertions(+), 42 deletions(-) diff --git a/internal/service/handlers/claim_event.go b/internal/service/handlers/claim_event.go index d326d64..8b8f4c7 100644 --- a/internal/service/handlers/claim_event.go +++ b/internal/service/handlers/claim_event.go @@ -154,17 +154,9 @@ func DoClaimEventUpdates( balance data.Balance, reward int64) (err error) { - refsCount, level := levels.LvlUp(balance.Level, reward+balance.Amount) - if refsCount > 0 { - count, err := referralsQ.New().FilterByNullifier(balance.Nullifier).Count() - if err != nil { - return fmt.Errorf("failed to get referral count: %w", err) - } - - refToAdd := prepareReferralsToAdd(balance.Nullifier, uint64(refsCount), count) - if err = referralsQ.New().Insert(refToAdd...); err != nil { - return fmt.Errorf("failed to insert referrals: %w", err) - } + level, err := doLvlUpAndReferralsUpdate(levels, referralsQ, balance, reward) + if err != nil { + return fmt.Errorf("failed to do lvlup and referrals updates: %w", err) } err = balancesQ.FilterByNullifier(balance.Nullifier).Update(map[string]any{ @@ -185,6 +177,23 @@ func DoClaimEventUpdates( return nil } +func doLvlUpAndReferralsUpdate(levels config.Levels, referralsQ data.ReferralsQ, balance data.Balance, reward int64) (level int, err error) { + refsCount, level := levels.LvlUp(balance.Level, reward+balance.Amount) + if refsCount > 0 { + count, err := referralsQ.New().FilterByNullifier(balance.Nullifier).Count() + if err != nil { + return 0, fmt.Errorf("failed to get referral count: %w", err) + } + + refToAdd := prepareReferralsToAdd(balance.Nullifier, uint64(refsCount), count) + if err = referralsQ.New().Insert(refToAdd...); err != nil { + return 0, fmt.Errorf("failed to insert referrals: %w", err) + } + } + + return level, nil +} + func newClaimEventResponse( event data.Event, meta resources.EventStaticMeta, diff --git a/internal/service/handlers/create_balance.go b/internal/service/handlers/create_balance.go index 7303837..88f9af8 100644 --- a/internal/service/handlers/create_balance.go +++ b/internal/service/handlers/create_balance.go @@ -51,10 +51,8 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { return } - referrals := prepareReferralsToAdd(nullifier, 5, 0) - events := prepareEventsWithRef(nullifier, req.Data.Attributes.ReferredBy, r) - if err = createBalanceWithEventsAndReferrals(nullifier, req.Data.Attributes.ReferredBy, events, referrals, r); err != nil { + if err = createBalanceWithEventsAndReferrals(nullifier, req.Data.Attributes.ReferredBy, events, r); err != nil { Log(r).WithError(err).Error("Failed to create balance with events") ape.RenderErr(w, problems.InternalError()) return @@ -71,8 +69,14 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { return } - for i := range referrals { - referrals[i].Status = data.StatusActive + referrals, err := ReferralsQ(r). + FilterByNullifier(nullifier). + WithStatus(). + Select() + if err != nil { + Log(r).WithError(err).Error("Failed to get referrals by nullifier with rewarding field") + ape.RenderErr(w, problems.InternalError()) + return } ape.Render(w, newBalanceResponse(*balance, referrals)) @@ -100,14 +104,16 @@ func prepareEventsWithRef(nullifier, refBy string, r *http.Request) []data.Event }) } +// createBalanceWithEvents should be called in transaction to avoid database corruption func createBalanceWithEvents(nullifier, refBy string, events []data.Event, r *http.Request) error { return EventsQ(r).Transaction(func() error { - err := BalancesQ(r).Insert(data.Balance{ + balance := data.Balance{ Nullifier: nullifier, ReferredBy: sql.NullString{String: refBy, Valid: refBy != ""}, - Level: Levels(r).MinLvl(), - }) + Level: 0, + } + err := BalancesQ(r).Insert(balance) if err != nil { return fmt.Errorf("add balance: %w", err) } @@ -117,18 +123,24 @@ func createBalanceWithEvents(nullifier, refBy string, events []data.Event, r *ht return fmt.Errorf("add open events: %w", err) } + Log(r).Debugf("%s referral will be consumed", refBy) + if _, err = ReferralsQ(r).Consume(refBy); err != nil { + return fmt.Errorf("consume referral: %w", err) + } + return nil }) } -func createBalanceWithEventsAndReferrals(nullifier, refBy string, events []data.Event, refCodes []data.Referral, r *http.Request) error { +func createBalanceWithEventsAndReferrals(nullifier, refBy string, events []data.Event, r *http.Request) error { return EventsQ(r).Transaction(func() error { - err := BalancesQ(r).Insert(data.Balance{ + balance := data.Balance{ Nullifier: nullifier, ReferredBy: sql.NullString{String: refBy, Valid: refBy != ""}, - Level: Levels(r).MinLvl(), - }) + Level: 0, + } + err := BalancesQ(r).Insert(balance) if err != nil { return fmt.Errorf("add balance: %w", err) } @@ -138,16 +150,23 @@ func createBalanceWithEventsAndReferrals(nullifier, refBy string, events []data. return fmt.Errorf("add open events: %w", err) } - Log(r).Debugf("%d referrals will be added for nullifier=%s", len(refCodes), nullifier) - if err = ReferralsQ(r).Insert(refCodes...); err != nil { - return fmt.Errorf("add referrals: %w", err) - } - Log(r).Debugf("%s referral will be consumed", refBy) if _, err = ReferralsQ(r).Consume(refBy); err != nil { return fmt.Errorf("consume referral: %w", err) } + level, err := doLvlUpAndReferralsUpdate(Levels(r), ReferralsQ(r), balance, 0) + if err != nil { + return fmt.Errorf("failed to do lvlup and referrals update: %w", err) + } + + err = BalancesQ(r).FilterByNullifier(balance.Nullifier).Update(map[string]any{ + data.ColLevel: level, + }) + if err != nil { + return fmt.Errorf("update balance amount and level: %w", err) + } + return nil }) } diff --git a/internal/service/handlers/verify_passport.go b/internal/service/handlers/verify_passport.go index d0f4388..6fff199 100644 --- a/internal/service/handlers/verify_passport.go +++ b/internal/service/handlers/verify_passport.go @@ -335,7 +335,23 @@ func addEventForReferrer(r *http.Request, evTypeRef *evtypes.EventConfig, balanc return fmt.Errorf("critical: referred_by not null, but row in referrals absent") } - if !evTypeRef.AutoClaim { + referrerBalance, err := BalancesQ(r).FilterByNullifier(referral.Nullifier).Get() + if err != nil { + return fmt.Errorf("failed to get referrer balance: %w", err) + } + if referrerBalance == nil { + return fmt.Errorf("critical: referrer balance not exist [%s], while referral code exist", referral.Nullifier) + } + + if !referrerBalance.ReferredBy.Valid { + Log(r).Debug("Referrer is genesis balance") + return nil + } + + if !evTypeRef.AutoClaim || referrerBalance.Country == nil { + if referrerBalance.Country == nil { + Log(r).Debug("Referrer not scan passport yet! Add fulfilled events") + } err = EventsQ(r).Insert(data.Event{ Nullifier: referral.Nullifier, Type: evTypeRef.Name, @@ -349,19 +365,6 @@ func addEventForReferrer(r *http.Request, evTypeRef *evtypes.EventConfig, balanc return nil } - referrerBalance, err := BalancesQ(r).FilterByNullifier(referral.Nullifier).Get() - if err != nil { - return fmt.Errorf("failed to get referrer balance: %w", err) - } - if referrerBalance == nil { - return fmt.Errorf("critical: referrer balance not exist [%s], while referral code exist", referral.Nullifier) - } - - if !referrerBalance.ReferredBy.Valid || referrerBalance.Country == nil { - Log(r).Debug("Referrer is genesis balance or not scanned passport") - return nil - } - country, err := CountriesQ(r).FilterByCodes(*referrerBalance.Country).Get() if err != nil { return fmt.Errorf("failed to get referrer country: %w", err) From e8e65fb8049a041761397dd429c9949addf583de Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Wed, 19 Jun 2024 14:38:03 +0300 Subject: [PATCH 12/22] fix genesis balance creation --- internal/service/handlers/create_balance.go | 38 ++++++++++----------- internal/service/handlers/edit_referrals.go | 31 ++++++++++------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/internal/service/handlers/create_balance.go b/internal/service/handlers/create_balance.go index 88f9af8..fb4d3c7 100644 --- a/internal/service/handlers/create_balance.go +++ b/internal/service/handlers/create_balance.go @@ -106,30 +106,28 @@ func prepareEventsWithRef(nullifier, refBy string, r *http.Request) []data.Event // createBalanceWithEvents should be called in transaction to avoid database corruption func createBalanceWithEvents(nullifier, refBy string, events []data.Event, r *http.Request) error { - return EventsQ(r).Transaction(func() error { - balance := data.Balance{ - Nullifier: nullifier, - ReferredBy: sql.NullString{String: refBy, Valid: refBy != ""}, - Level: 0, - } + balance := data.Balance{ + Nullifier: nullifier, + ReferredBy: sql.NullString{String: refBy, Valid: refBy != ""}, + Level: 0, + } - err := BalancesQ(r).Insert(balance) - if err != nil { - return fmt.Errorf("add balance: %w", err) - } + err := BalancesQ(r).Insert(balance) + if err != nil { + return fmt.Errorf("add balance: %w", err) + } - Log(r).Debugf("%d events will be added for nullifier=%s", len(events), nullifier) - if err = EventsQ(r).Insert(events...); err != nil { - return fmt.Errorf("add open events: %w", err) - } + Log(r).Debugf("%d events will be added for nullifier=%s", len(events), nullifier) + if err = EventsQ(r).Insert(events...); err != nil { + return fmt.Errorf("add open events: %w", err) + } - Log(r).Debugf("%s referral will be consumed", refBy) - if _, err = ReferralsQ(r).Consume(refBy); err != nil { - return fmt.Errorf("consume referral: %w", err) - } + Log(r).Debugf("%s referral will be consumed", refBy) + if _, err = ReferralsQ(r).Consume(refBy); err != nil { + return fmt.Errorf("consume referral: %w", err) + } - return nil - }) + return nil } func createBalanceWithEventsAndReferrals(nullifier, refBy string, events []data.Event, r *http.Request) error { diff --git a/internal/service/handlers/edit_referrals.go b/internal/service/handlers/edit_referrals.go index 05392e2..4bb5377 100644 --- a/internal/service/handlers/edit_referrals.go +++ b/internal/service/handlers/edit_referrals.go @@ -32,21 +32,28 @@ func EditReferrals(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) return } - events := prepareEventsWithRef(req.Nullifier, "", r) - if err = createBalanceWithEvents(req.Nullifier, "", events, r); err != nil { - Log(r).WithError(err).Error("Failed to create balance with events") - ape.RenderErr(w, problems.InternalError()) - return - } - code := referralid.New(req.Nullifier, 0) - err = ReferralsQ(r).Insert(data.Referral{ - ID: code, - Nullifier: req.Nullifier, - UsageLeft: int32(req.Count), + var code string + err = EventsQ(r).Transaction(func() error { + events := prepareEventsWithRef(req.Nullifier, "", r) + if err = createBalanceWithEvents(req.Nullifier, "", events, r); err != nil { + return fmt.Errorf("failed to create balance with events: %w", err) + } + + code = referralid.New(req.Nullifier, 0) + err = ReferralsQ(r).Insert(data.Referral{ + ID: code, + Nullifier: req.Nullifier, + UsageLeft: int32(req.Count), + }) + if err != nil { + return fmt.Errorf("failed to insert referral for nullifier [%s]: %w", req.Nullifier, err) + } + + return nil }) if err != nil { - Log(r).WithError(err).Errorf("failed to insert referral for nullifier [%s]", req.Nullifier) + Log(r).WithError(err).Errorf("failed to create genesis balance [%s]", req.Nullifier) ape.RenderErr(w, problems.InternalError()) return } From b8b539b4adead3905048c1174950c9ddf835bf5f Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 12:58:20 +0300 Subject: [PATCH 13/22] Fix bag with level accruing, because map can be indexed by for in different order each execution --- internal/config/levels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/levels.go b/internal/config/levels.go index 70722ff..3cdd9f4 100644 --- a/internal/config/levels.go +++ b/internal/config/levels.go @@ -52,7 +52,7 @@ func (l Levels) LvlUp(currentLevel int, totalAmount int64) (refCoundToAdd int, n continue } if int64(v.Threshold) > totalAmount { - break + continue } refCoundToAdd += v.Referrals From 1bb517f27cc0956b855e67ea36119bf8d6009e29 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 13:37:17 +0300 Subject: [PATCH 14/22] Tests fix & refactoring --- config.yaml | 4 +- requests_test.go | 278 ++++++++++++++++++++--------------------------- 2 files changed, 120 insertions(+), 162 deletions(-) diff --git a/config.yaml b/config.yaml index e8a0118..08dccaf 100644 --- a/config.yaml +++ b/config.yaml @@ -70,8 +70,8 @@ countries: withdrawal_allowed: true - code: "USA" reserve_limit: 5 - reserve_allowed: false - withdrawal_allowed: false + reserve_allowed: true + withdrawal_allowed: true - code: "GBR" reserve_limit: 5 reserve_allowed: false diff --git a/requests_test.go b/requests_test.go index 589426e..0d7f15a 100644 --- a/requests_test.go +++ b/requests_test.go @@ -28,6 +28,7 @@ import ( zk "github.com/rarimo/zkverifier-kit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gitlab.com/distributed_lab/figure" "gitlab.com/distributed_lab/kit/kv" ) @@ -83,12 +84,6 @@ func TestMain(m *testing.M) { exitVal = m.Run() } -func tearDown() { -} - -func tearDown() { -} - func setUp() { if os.Getenv(kv.EnvViperConfigFile) == "" { err := os.Setenv(kv.EnvViperConfigFile, defaultConfigFile) @@ -97,10 +92,14 @@ func setUp() { } } + var err error + apiURL, err = getApiURL() + if err != nil { + panic(fmt.Errorf("failed to get Api URL: %w", err)) + } + globalCfg = config.New(kv.MustFromEnv()) - apiURL = fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1", globalCfg.Listener().Addr().String()) initGenesisRef() - // let's not introduce counting function just for test balances, err := pg.NewBalances(globalCfg.DB()).Select() if err != nil { @@ -109,13 +108,27 @@ func setUp() { // to prevent repeating cleanups, more balances are created currentNullifierIndex = len(balances) - nullifiers = make([]string, 20) + nullifiers = make([]string, 100) for i := range nullifiers { hash := sha256.Sum256([]byte{byte(i + len(balances))}) nullifiers[i] = hexutil.Encode(hash[:]) } } +func getApiURL() (string, error) { + var cfg struct { + Addr string `fig:"addr,required"` + } + + err := figure.Out(&cfg).From(kv.MustGetStringMap(kv.MustFromEnv(), "listener")).Please() + if err != nil { + return "", fmt.Errorf("failed to figure out listener from service config: %w", err) + } + + apiURL := fmt.Sprintf("http://%s/integrations/rarime-points-svc/v1/", cfg.Addr) + return apiURL, nil +} + func initGenesisRef() { gen, err := pg.NewReferrals(globalCfg.DB()). FilterConsumed(). @@ -146,28 +159,6 @@ func TestCreateBalance(t *testing.T) { otRefCode string ) - validBalanceChecks := func(t *testing.T, nullifier, code string) { - resp, err := createBalance(nullifier, code) - require.NoError(t, err) - require.Equal(t, nullifier, resp.Data.ID) - - attr := resp.Data.Attributes - - require.NotNil(t, attr.IsDisabled) - require.NotNil(t, attr.IsVerified) - require.NotNil(t, attr.ReferralCodes) - require.NotEmpty(t, *attr.ReferralCodes) - - assert.Equal(t, 0, attr.Amount) - assert.False(t, *attr.IsDisabled) - assert.False(t, *attr.IsVerified) - assert.Equal(t, 1, attr.Level) - assert.NotNil(t, attr.Rank) - - otRefCode = (*attr.ReferralCodes)[0].Id - require.NotEmpty(t, otRefCode) - } - // fixme @violog: looks like fail on assert/require won't stop outer tests, must check before proceeding t.Run("BalanceGenesisCode", func(t *testing.T) { resp := createAndValidateBalance(t, nullifierShared, genesisCode) @@ -180,7 +171,7 @@ func TestCreateBalance(t *testing.T) { t.Run("SameBalanceConflict", func(t *testing.T) { _, err := createBalance(nullifierShared, genesisCode) - var apiErr jsonapi.ErrorObject + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) assert.Equal(t, "409", apiErr.Status) }) @@ -189,22 +180,21 @@ func TestCreateBalance(t *testing.T) { n1, n2 := nextN(), nextN() body := createBalanceBody(n1, genesisCode) err := requestWithBody(balancesEndpoint, "POST", n2, body, nil) - - var apiErr jsonapi.ErrorObject + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) assert.Equal(t, "401", apiErr.Status) }) t.Run("ConsumedCode", func(t *testing.T) { _, err := createBalance(nextN(), otRefCode) - var apiErr jsonapi.ErrorObject + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) assert.Equal(t, "404", apiErr.Status) }) t.Run("IncorrectCode", func(t *testing.T) { _, err := createBalance(nextN(), "invalid") - var apiErr jsonapi.ErrorObject + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) assert.Equal(t, "404", apiErr.Status) }) @@ -224,7 +214,7 @@ func createAndValidateBalance(t *testing.T, nullifier, code string) resources.Ba require.NotNil(t, attr.ReferralCodes) require.NotEmpty(t, *attr.ReferralCodes) - assert.Equal(t, 0, attr.Amount) + assert.Equal(t, int64(0), attr.Amount) assert.False(t, *attr.IsDisabled) assert.False(t, *attr.IsVerified) assert.Equal(t, 1, attr.Level) @@ -245,25 +235,27 @@ func TestVerifyPassport(t *testing.T) { createAndValidateBalance(t, referee, (*balance1.Data.Attributes.ReferralCodes)[0].Id) var countriesResp resources.CountriesConfigResponse - err := getRequest("public/countries", nil, "", &countriesResp) + err := getRequest("public/countries_config", nil, "", &countriesResp) require.NoError(t, err) countriesList := countriesResp.Data.Attributes.Countries - require.Contains(t, countriesList, ukrCode) - require.Contains(t, countriesList, usaCode) + var ukr, can bool // ensure the same behaviour whitelisted and banned countries for _, c := range countriesList { - if c.Code == ukrCode { + if c.Code == "UKR" { + ukr = true require.True(t, c.ReserveAllowed) require.True(t, c.WithdrawalAllowed) continue } - if c.Code == usaCode { - require.False(t, c.ReserveAllowed) + if c.Code == "CAN" { + can = true + require.False(t, c.ReserveAllowed) // change this require.False(t, c.WithdrawalAllowed) } } + require.False(t, !ukr || !can) // passport verification should lead to referral event appearance and claimed passport event t.Run("VerifyPassport", func(t *testing.T) { @@ -275,7 +267,7 @@ func TestVerifyPassport(t *testing.T) { t.Run("VerifyPassportSecondTime", func(t *testing.T) { _, err = verifyPassport(referee, ukrCode) - var apiErr jsonapi.ErrorObject + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) assert.Equal(t, "429", apiErr.Status) getAndValidateBalance(t, referee, true) @@ -283,18 +275,20 @@ func TestVerifyPassport(t *testing.T) { t.Run("IncorrectCountryCode", func(t *testing.T) { n := nextN() + createAndValidateBalance(t, n, genesisCode) _, err = verifyPassport(n, "6974819") - var apiErr jsonapi.ErrorObject + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) - assert.Equal(t, "429", apiErr.Status) + assert.Equal(t, "500", apiErr.Status) getAndValidateBalance(t, n, false) }) t.Run("BlacklistedCountry", func(t *testing.T) { n := nextN() - resp, err := verifyPassport(n, usaCode) + createAndValidateBalance(t, n, genesisCode) + resp, err := verifyPassport(n, canCode) require.NoError(t, err) - assert.True(t, resp.Data.Attributes.Claimed) + assert.False(t, resp.Data.Attributes.Claimed) getAndValidateBalance(t, n, true) }) } @@ -329,7 +323,7 @@ func TestEventsAutoClaim(t *testing.T) { respBalance, err := getBalance(n) require.NoError(t, err) require.Equal(t, 2, respBalance.Data.Attributes.Level) - require.Equal(t, 5, respBalance.Data.Attributes.Amount) + require.Equal(t, int64(5), respBalance.Data.Attributes.Amount) require.NotNil(t, respBalance.Data.Attributes.ReferralCodes) require.Equal(t, 10, len(*respBalance.Data.Attributes.ReferralCodes)) }) @@ -382,10 +376,10 @@ func TestEventsAutoClaim(t *testing.T) { _, err = verifyPassport(n2, ukrCode) require.NoError(t, err) - respEvents, err := getEvents(n1) + respEvents, err := getEvents(n1, evtypes.TypeReferralSpecific) require.NoError(t, err) - _, status := getEventFromList(respEvents, evtypes.TypeReferralSpecific) - require.Equal(t, data.EventClaimed, status) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventClaimed), respEvents.Data[0].Attributes.Status) }) // User can have a lot unclaimed fulfilled referral specific events if user not scan passport @@ -394,23 +388,24 @@ func TestEventsAutoClaim(t *testing.T) { respBalance, err := createBalance(n1, genesisCode) require.NoError(t, err) require.NotNil(t, respBalance.Data.Attributes.ReferralCodes) - require.GreaterOrEqual(t, (*respBalance.Data.Attributes.ReferralCodes), 2) + require.GreaterOrEqual(t, len(*respBalance.Data.Attributes.ReferralCodes), 2) - respBalance, err = createBalance(n2, (*respBalance.Data.Attributes.ReferralCodes)[0].Id) + _, err = createBalance(n2, (*respBalance.Data.Attributes.ReferralCodes)[0].Id) require.NoError(t, err) _, err = verifyPassport(n2, ukrCode) require.NoError(t, err) - respBalance, err = createBalance(n3, (*respBalance.Data.Attributes.ReferralCodes)[1].Id) + _, err = createBalance(n3, (*respBalance.Data.Attributes.ReferralCodes)[1].Id) require.NoError(t, err) _, err = verifyPassport(n3, ukrCode) require.NoError(t, err) - respEvents, err := getEvents(n1) + respEvents, err := getEvents(n1, evtypes.TypeReferralSpecific) require.NoError(t, err) + require.Equal(t, 2, len(respEvents.Data)) fulfilledEventCount := 0 for _, event := range respEvents.Data { - if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventFulfilled) { + if event.Attributes.Status == string(data.EventFulfilled) { fulfilledEventCount += 1 } } @@ -420,11 +415,12 @@ func TestEventsAutoClaim(t *testing.T) { require.NoError(t, err) require.True(t, respVerifyStatus.Data.Attributes.Claimed) - respEvents, err = getEvents(n1) + respEvents, err = getEvents(n1, evtypes.TypeReferralSpecific) require.NoError(t, err) + require.Equal(t, 2, len(respEvents.Data)) claimedEventCount := 0 for _, event := range respEvents.Data { - if event.Attributes.Meta.Static.Name == evtypes.TypeReferralSpecific && event.Attributes.Status == string(data.EventFulfilled) { + if event.Attributes.Status == string(data.EventClaimed) { claimedEventCount += 1 } } @@ -438,13 +434,13 @@ func TestClaimEvent(t *testing.T) { _, err := createBalance(n, genesisCode) require.NoError(t, err) - respEvents, err := getEvents(n) + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) require.NoError(t, err) - eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) - require.Equal(t, data.EventFulfilled, status) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventFulfilled), respEvents.Data[0].Attributes.Status) - _, err = claimEvent(eventID, n) - var apiErr jsonapi.ErrorObject + _, err = claimEvent(respEvents.Data[0].ID, n) + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "403", apiErr.Status) }) @@ -457,8 +453,8 @@ func TestClaimEvent(t *testing.T) { _, err = verifyPassport(n, ukrCode) require.NoError(t, err) - _, err = claimEvent("event", n) - var apiErr jsonapi.ErrorObject + _, err = claimEvent("e174d6e2-0c81-4771-99a1-8447532143b8", n) + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "404", apiErr.Status) }) @@ -471,14 +467,14 @@ func TestClaimEvent(t *testing.T) { _, err = verifyPassport(n, fraCode) require.NoError(t, err) - respEvents, err := getEvents(n) + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) require.NoError(t, err) - eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) - require.Equal(t, data.EventFulfilled, status) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventFulfilled), respEvents.Data[0].Attributes.Status) - respEvent, err := claimEvent(eventID, n) + respEvent, err := claimEvent(respEvents.Data[0].ID, n) require.NoError(t, err) - require.Equal(t, data.EventClaimed, respEvent.Data.Attributes.Status) + require.Equal(t, string(data.EventClaimed), respEvent.Data.Attributes.Status) }) // this test depend on previous test @@ -490,13 +486,13 @@ func TestClaimEvent(t *testing.T) { _, err = verifyPassport(n, fraCode) require.NoError(t, err) - respEvents, err := getEvents(n) + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) require.NoError(t, err) - eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) - require.Equal(t, data.EventFulfilled, status) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventFulfilled), respEvents.Data[0].Attributes.Status) - _, err = claimEvent(eventID, n) - var apiErr jsonapi.ErrorObject + _, err = claimEvent(respEvents.Data[0].ID, n) + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "403", apiErr.Status) }) @@ -509,13 +505,13 @@ func TestClaimEvent(t *testing.T) { _, err = verifyPassport(n, indCode) require.NoError(t, err) - respEvents, err := getEvents(n) + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) require.NoError(t, err) - eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) - require.Equal(t, data.EventFulfilled, status) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventFulfilled), respEvents.Data[0].Attributes.Status) - _, err = claimEvent(eventID, n) - var apiErr jsonapi.ErrorObject + _, err = claimEvent(respEvents.Data[0].ID, n) + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "403", apiErr.Status) }) @@ -528,13 +524,13 @@ func TestClaimEvent(t *testing.T) { _, err = verifyPassport(n, mcoCode) require.NoError(t, err) - respEvents, err := getEvents(n) + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) require.NoError(t, err) - eventID, status := getEventFromList(respEvents, evtypes.TypeFreeWeekly) - require.Equal(t, data.EventFulfilled, status) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventFulfilled), respEvents.Data[0].Attributes.Status) - _, err = claimEvent(eventID, n) - var apiErr jsonapi.ErrorObject + _, err = claimEvent(respEvents.Data[0].ID, n) + var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "403", apiErr.Status) }) @@ -563,8 +559,8 @@ func TestLevels(t *testing.T) { require.Equal(t, 2, lvl2Cfg.Level) require.Equal(t, 3, lvl3Cfg.Level) // rewards must be equal to level threshold in order to upgrade level for each of 2 claimed events - require.Equal(t, amountClaim1, lvl2Cfg.Threshold) - require.Equal(t, amountClaim2, lvl3Cfg.Threshold) + require.Equal(t, amountClaim1, int64(lvl2Cfg.Threshold)) + require.Equal(t, amountClaim2, int64(lvl3Cfg.Threshold)) require.False(t, evTypeWeekly.AutoClaim) createAndValidateBalance(t, nullifier, genesisCode) @@ -592,7 +588,7 @@ func TestLevels(t *testing.T) { assert.Equal(t, lvl2Referrals, len(*refCodes)) eventID = getAndValidateSingleEvent(t, nullifier, evtypes.TypeFreeWeekly, data.EventFulfilled) - claimEventAndValidate(t, eventID, nullifier, amountClaim2) + claimEventAndValidate(t, eventID, nullifier, 1) balance = getAndValidateBalance(t, nullifier, true) balanceAttr = balance.Data.Attributes @@ -605,7 +601,7 @@ func TestLevels(t *testing.T) { } func getAndValidateSingleEvent(t *testing.T, nullifier, evType string, status data.EventStatus) string { - resp, err := getEvents(nullifier, evtypes.TypePassportScan) + resp, err := getEvents(nullifier, evType) require.NoError(t, err) require.Len(t, resp.Data, 1) @@ -614,7 +610,7 @@ func getAndValidateSingleEvent(t *testing.T, nullifier, evType string, status da require.NotEmpty(t, event.ID) assert.Equal(t, evType, attr.Meta.Static.Name) - assert.Equal(t, status, attr.Status) + assert.Equal(t, string(status), attr.Status) return event.ID } @@ -622,85 +618,47 @@ func claimEventAndValidate(t *testing.T, id, nullifier string, reward int64) { resp, err := claimEvent(id, nullifier) require.NoError(t, err) attr := resp.Data.Attributes - assert.Equal(t, data.EventClaimed, attr.Status) + assert.Equal(t, string(data.EventClaimed), attr.Status) require.NotNil(t, attr.PointsAmount) assert.Equal(t, reward, *attr.PointsAmount) } -func TestCountryPoolsDefault(t *testing.T) { - nullifier := "0x0000000000000000000000000000000000000000000000000000000000100000" +// func TestCountryPoolsDefault(t *testing.T) { +// nullifier := "0x0000000000000000000000000000000000000000000000000000000000100000" - createBalance(t, nullifier, genesisCode) - verifyPassport(t, nullifier, deuCode) +// createBalance(t, nullifier, genesisCode) +// verifyPassport(t, nullifier, deuCode) - t.Run("DefaultOverLimit", func(t *testing.T) { - freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) - if freeWeeklyEventID == "" { - t.Fatalf("free weekly event absent for %s", nullifier) - } +// t.Run("DefaultOverLimit", func(t *testing.T) { +// freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) +// if freeWeeklyEventID == "" { +// t.Fatalf("free weekly event absent for %s", nullifier) +// } - body := claimEventBody(freeWeeklyEventID) - _, respCode := requestWithBody(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) - if respCode != http.StatusForbidden { - t.Errorf("want %d got %d", http.StatusForbidden, respCode) - } - }) -} - -func getEventFromList(events resources.EventListResponse, evtype string) (id, status string) { - for _, event := range events.Data { - if event.Attributes.Meta.Static.Name == evtype { - return event.ID, event.Attributes.Status - } - } - return "", "" -} +// body := claimEventBody(freeWeeklyEventID) +// _, respCode := requestWithBody(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) +// if respCode != http.StatusForbidden { +// t.Errorf("want %d got %d", http.StatusForbidden, respCode) +// } +// }) +// } func claimEvent(id, nullifier string) (resp resources.EventResponse, err error) { body := claimEventBody(id) - respBody, respCode := requestWithBody(t, eventsEndpoint+"/"+id, body, nullifier, true) - if respCode != http.StatusOK { - t.Errorf("want %d got %d", http.StatusOK, respCode) - } - - var event resources.EventResponse - err := json.Unmarshal(respBody, &event) - if err != nil { - t.Fatalf("failed to unmarhal event response: %v", err) - } - - return event -} - -func verifyPassport(t *testing.T, nullifier, country string) { - proof := baseProof - proof.PubSignals[zk.Citizenship] = country - body := verifyPassportBody(nullifier, proof) - - _, respCode := requestWithBody(t, balancesEndpoint+"/"+nullifier+"/verifypassport", body, nullifier, false) - if respCode != http.StatusNoContent { - t.Errorf("failed to verify passport: want %d got %d", http.StatusNoContent, respCode) - } + err = requestWithBody(eventsEndpoint+"/"+id, "PATCH", nullifier, body, &resp) + return } -func getEvents(t *testing.T, nullifier string) resources.EventListResponse { - respBody, respCode := getRequest(t, - eventsEndpoint, func() url.Values { - query := url.Values{} - query.Add("filter[nullifier]", nullifier) - return query - }(), nullifier) - if respCode != http.StatusOK { - t.Errorf("failed to get events: want %d got %d", http.StatusOK, respCode) - } - - var events resources.EventListResponse - err := json.Unmarshal(respBody, &events) - if err != nil { - t.Fatalf("failed to unmarhal event list response: %v", err) +func getEvents(nullifier string, types ...string) (resp resources.EventListResponse, err error) { + query := url.Values{} + query.Add("filter[nullifier]", nullifier) + query.Add("page[limit]", "100") + if len(types) > 0 { + query.Add("filter[meta.static.name]", strings.Join(types, ",")) } - return events + err = getRequest(eventsEndpoint, query, nullifier, &resp) + return } func createBalance(nullifier, code string) (resp resources.BalanceResponse, err error) { @@ -722,7 +680,7 @@ func verifyPassport(nullifier, country string) (resp resources.PassportEventStat proof := baseProof proof.PubSignals[zk.Citizenship] = country body := verifyPassportBody(nullifier, proof) - err = requestWithBody(balancesEndpoint, "POST", nullifier, body, &resp) + err = requestWithBody(balancesEndpoint+"/"+nullifier+"/verifypassport", "POST", nullifier, body, &resp) return } @@ -733,9 +691,9 @@ type editReferralsResponse struct { func editReferrals(nullifier string, count uint64) (resp editReferralsResponse, err error) { req := requests.EditReferralsRequest{Nullifier: nullifier, Count: count} - - err = requestWithBody(apiURL+"/private/referrals", "POST", "", req, &resp) - + fmt.Println(req) + err = requestWithBody("private/referrals", "POST", "", req, &resp) + fmt.Println(resp) return } From 7b42bd644e57d484eb26520e010a9ec45158de0a Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 13:53:22 +0300 Subject: [PATCH 15/22] Country pools tests --- requests_test.go | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/requests_test.go b/requests_test.go index 0d7f15a..75e6e19 100644 --- a/requests_test.go +++ b/requests_test.go @@ -251,7 +251,7 @@ func TestVerifyPassport(t *testing.T) { } if c.Code == "CAN" { can = true - require.False(t, c.ReserveAllowed) // change this + require.False(t, c.ReserveAllowed) require.False(t, c.WithdrawalAllowed) } } @@ -623,25 +623,31 @@ func claimEventAndValidate(t *testing.T, id, nullifier string, reward int64) { assert.Equal(t, reward, *attr.PointsAmount) } -// func TestCountryPoolsDefault(t *testing.T) { -// nullifier := "0x0000000000000000000000000000000000000000000000000000000000100000" - -// createBalance(t, nullifier, genesisCode) -// verifyPassport(t, nullifier, deuCode) - -// t.Run("DefaultOverLimit", func(t *testing.T) { -// freeWeeklyEventID, _ := getEventFromList(getEvents(t, nullifier), evtypes.TypeFreeWeekly) -// if freeWeeklyEventID == "" { -// t.Fatalf("free weekly event absent for %s", nullifier) -// } - -// body := claimEventBody(freeWeeklyEventID) -// _, respCode := requestWithBody(t, eventsEndpoint+"/"+freeWeeklyEventID, body, nullifier, true) -// if respCode != http.StatusForbidden { -// t.Errorf("want %d got %d", http.StatusForbidden, respCode) -// } -// }) -// } +// test only default config because main logic already tested in another tests (autoclaim, claim, verifypassport) +func TestCountryPoolsDefault(t *testing.T) { + n := nextN() + createAndValidateBalance(t, n, genesisCode) + + t.Run("DefaultUnderLimit", func(t *testing.T) { + resp, err := verifyPassport(n, deuCode) + require.NoError(t, err) + assert.True(t, resp.Data.Attributes.Claimed) + getAndValidateBalance(t, n, true) + + }) + + t.Run("DefaultOverLimit", func(t *testing.T) { + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) + require.NoError(t, err) + require.Equal(t, 1, len(respEvents.Data)) + require.Equal(t, string(data.EventFulfilled), respEvents.Data[0].Attributes.Status) + + _, err = claimEvent(respEvents.Data[0].ID, n) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "403", apiErr.Status) + }) +} func claimEvent(id, nullifier string) (resp resources.EventResponse, err error) { body := claimEventBody(id) From d0020076f85b77a715a4ac6332fcd27994a3861c Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 15:08:30 +0300 Subject: [PATCH 16/22] Referral status tests --- README.md | 2 +- config-testing.yaml | 40 ++++++++++++--- config.yaml | 43 +--------------- requests_test.go | 119 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 150 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 356f1b9..40c005b 100644 --- a/README.md +++ b/README.md @@ -123,4 +123,4 @@ and in handlers/verify_passport: // } ``` -Run service with standart config (you need to configure db url only) and run tests. \ No newline at end of file +Run service with config-testing.yaml (you need to configure db url) and run tests. \ No newline at end of file diff --git a/config-testing.yaml b/config-testing.yaml index fdfcfe2..e22629e 100644 --- a/config-testing.yaml +++ b/config-testing.yaml @@ -11,7 +11,7 @@ listener: event_types: types: - name: free_weekly - reward: 5 + reward: 1 frequency: weekly title: Free weekly points short_description: Get free points every week, just pressing the button @@ -19,13 +19,14 @@ event_types: logo: https://pbs.twimg.com/profile_images/1639021161257263105/XmT0EBnK_400x400.jpg starts_at: 2024-03-23T12:42:00Z - name: passport_scan - reward: 10 + reward: 5 frequency: one-time title: Passport verification short_description: Scan your passport to unlock features and get points description: "## Passport verification\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" logo: https://pbs.twimg.com/profile_images/1639021161257263105/XmT0EBnK_400x400.jpg starts_at: 2024-03-23T12:42:00Z + auto_claim: true - name: referral_common reward: 15 frequency: one-time @@ -34,13 +35,14 @@ event_types: description: "## Referral program\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" action_url: https://rarimo.com - name: referral_specific - reward: 3 + reward: 1 frequency: unlimited no_auto_open: true title: Refer user short_description: The user has verified the passport. Claim the reward! description: "## Referral program\n\nThis is a general event description.\n\n### How it works\n\n- Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n- Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nParticipate every week and maximize your rewards!\n" action_url: https://rarimo.com + auto_claim: true - name: new_limited_event title: Limited event reward: 5 @@ -65,22 +67,46 @@ levels: referrals: 5 withdrawal_allowed: false - lvl: 2 - threshold: 30 + threshold: 5 referrals: 5 withdrawal_allowed: true - lvl: 3 - threshold: 50 + threshold: 6 referrals: 5 withdrawal_allowed: true countries: countries: + - code: "UKR" + reserve_limit: 100000 + reserve_allowed: true + withdrawal_allowed: true - code: "USA" - reserve_limit: 0 + reserve_limit: 100 + reserve_allowed: false + withdrawal_allowed: false + - code: "GBR" + reserve_limit: 5 + reserve_allowed: false + withdrawal_allowed: true + - code: "CAN" + reserve_limit: 5 + reserve_allowed: true + withdrawal_allowed: true + - code: "FRA" + reserve_limit: 6 + reserve_allowed: true + withdrawal_allowed: true + - code: "IND" + reserve_limit: 1 + reserve_allowed: false + withdrawal_allowed: true + - code: "MCO" + reserve_limit: 100 reserve_allowed: false withdrawal_allowed: false - code: "default" - reserve_limit: 100000 + reserve_limit: 5 reserve_allowed: true withdrawal_allowed: true diff --git a/config.yaml b/config.yaml index 08dccaf..853776a 100644 --- a/config.yaml +++ b/config.yaml @@ -69,31 +69,11 @@ countries: reserve_allowed: true withdrawal_allowed: true - code: "USA" - reserve_limit: 5 - reserve_allowed: true - withdrawal_allowed: true - - code: "GBR" - reserve_limit: 5 - reserve_allowed: false - withdrawal_allowed: true - - code: "CAN" - reserve_limit: 100 - reserve_allowed: false - withdrawal_allowed: false - - code: "FRA" - reserve_limit: 6 - reserve_allowed: true - withdrawal_allowed: true - - code: "IND" - reserve_limit: 1 - reserve_allowed: false - withdrawal_allowed: true - - code: "MCO" - reserve_limit: 100 + reserve_limit: 0 reserve_allowed: false withdrawal_allowed: false - code: "default" - reserve_limit: 5 + reserve_limit: 100 reserve_allowed: true withdrawal_allowed: true @@ -116,22 +96,3 @@ root_verifier: withdrawal: point_price_urmo: 100 - -sbt_check: - networks: - - name: polygon - rpc: https://your-rpc - contract: 0x... - request_timeout: 5s - start_from_block: 48984542 - block_window: 3 - max_blocks_per_request: 5000 - - name: ethereum - rpc: https://your-rpc - contract: 0x... - request_timeout: 5s - - name: disabled_sample - disabled: true - rpc: https://your-rpc - contract: 0x... - request_timeout: 5s diff --git a/requests_test.go b/requests_test.go index 75e6e19..dda9b97 100644 --- a/requests_test.go +++ b/requests_test.go @@ -249,7 +249,7 @@ func TestVerifyPassport(t *testing.T) { require.True(t, c.WithdrawalAllowed) continue } - if c.Code == "CAN" { + if c.Code == "USA" { can = true require.False(t, c.ReserveAllowed) require.False(t, c.WithdrawalAllowed) @@ -286,7 +286,7 @@ func TestVerifyPassport(t *testing.T) { t.Run("BlacklistedCountry", func(t *testing.T) { n := nextN() createAndValidateBalance(t, n, genesisCode) - resp, err := verifyPassport(n, canCode) + resp, err := verifyPassport(n, usaCode) require.NoError(t, err) assert.False(t, resp.Data.Attributes.Claimed) getAndValidateBalance(t, n, true) @@ -316,7 +316,7 @@ func TestEventsAutoClaim(t *testing.T) { _, err := createBalance(n, genesisCode) require.NoError(t, err) - respVerifyStatus, err := verifyPassport(n, usaCode) + respVerifyStatus, err := verifyPassport(n, canCode) require.NoError(t, err) require.True(t, respVerifyStatus.Data.Attributes.Claimed) @@ -334,7 +334,7 @@ func TestEventsAutoClaim(t *testing.T) { _, err := createBalance(n, genesisCode) require.NoError(t, err) - respVerifyStatus, err := verifyPassport(n, usaCode) + respVerifyStatus, err := verifyPassport(n, canCode) require.NoError(t, err) require.False(t, respVerifyStatus.Data.Attributes.Claimed) }) @@ -354,7 +354,7 @@ func TestEventsAutoClaim(t *testing.T) { _, err := createBalance(n, genesisCode) require.NoError(t, err) - respVerifyStatus, err := verifyPassport(n, canCode) + respVerifyStatus, err := verifyPassport(n, usaCode) require.NoError(t, err) require.False(t, respVerifyStatus.Data.Attributes.Claimed) }) @@ -649,6 +649,115 @@ func TestCountryPoolsDefault(t *testing.T) { }) } +func TestReferralCodeStatuses(t *testing.T) { + t.Run("ActiveCode", func(t *testing.T) { + n := nextN() + respBalance := createAndValidateBalance(t, n, genesisCode) + require.Equal(t, 5, len(*respBalance.Data.Attributes.ReferralCodes)) + for _, v := range *respBalance.Data.Attributes.ReferralCodes { + require.Equal(t, data.StatusActive, v.Status) + } + }) + + t.Run("BannedCode", func(t *testing.T) { + n1, n2 := nextN(), nextN() + respBalance := createAndValidateBalance(t, n1, genesisCode) + respVerifyStatus, err := verifyPassport(n1, usaCode) + require.NoError(t, err) + require.False(t, respVerifyStatus.Data.Attributes.Claimed) + + refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id + _ = createAndValidateBalance(t, n2, refCode) + + respBalance = getAndValidateBalance(t, n1, true) + for _, v := range *respBalance.Data.Attributes.ReferralCodes { + if v.Id == refCode && v.Status == data.StatusBanned { + return + } + } + t.Fatal("Banned referral code absent") + }) + + t.Run("LimitedCode", func(t *testing.T) { + n1, n2 := nextN(), nextN() + respBalance := createAndValidateBalance(t, n1, genesisCode) + respVerifyStatus, err := verifyPassport(n1, gbrCode) + require.NoError(t, err) + require.False(t, respVerifyStatus.Data.Attributes.Claimed) + + refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id + _ = createAndValidateBalance(t, n2, refCode) + + respBalance = getAndValidateBalance(t, n1, true) + for _, v := range *respBalance.Data.Attributes.ReferralCodes { + if v.Id == refCode && v.Status == data.StatusLimited { + return + } + } + t.Fatal("Limited referral code absent") + }) + + t.Run("AwaitingCode", func(t *testing.T) { + n1, n2 := nextN(), nextN() + respBalance := createAndValidateBalance(t, n1, genesisCode) + + refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id + _ = createAndValidateBalance(t, n2, refCode) + respVerifyStatus, err := verifyPassport(n2, ukrCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) + + respBalance = getAndValidateBalance(t, n1, false) + for _, v := range *respBalance.Data.Attributes.ReferralCodes { + if v.Id == refCode && v.Status == data.StatusAwaiting { + return + } + } + t.Fatal("Awaiting referral code absent") + }) + + t.Run("RewardedCode", func(t *testing.T) { + n1, n2 := nextN(), nextN() + respBalance := createAndValidateBalance(t, n1, genesisCode) + respVerifyStatus, err := verifyPassport(n1, ukrCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) + + refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id + _ = createAndValidateBalance(t, n2, refCode) + respVerifyStatus, err = verifyPassport(n2, ukrCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) + + respBalance = getAndValidateBalance(t, n1, true) + for _, v := range *respBalance.Data.Attributes.ReferralCodes { + if v.Id == refCode && v.Status == data.StatusRewarded { + return + } + } + t.Fatal("Rewarded referral code absent") + }) + + t.Run("ConsumedCode", func(t *testing.T) { + n1, n2 := nextN(), nextN() + respBalance := createAndValidateBalance(t, n1, genesisCode) + respVerifyStatus, err := verifyPassport(n1, ukrCode) + require.NoError(t, err) + require.True(t, respVerifyStatus.Data.Attributes.Claimed) + + refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id + _ = createAndValidateBalance(t, n2, refCode) + + respBalance = getAndValidateBalance(t, n1, true) + for _, v := range *respBalance.Data.Attributes.ReferralCodes { + if v.Id == refCode && v.Status == data.StatusConsumed { + return + } + } + t.Fatal("Consumed referral code absent") + }) +} + func claimEvent(id, nullifier string) (resp resources.EventResponse, err error) { body := claimEventBody(id) err = requestWithBody(eventsEndpoint+"/"+id, "PATCH", nullifier, body, &resp) From e5ec3f266804e5b25eaf60c460d63a41e353618d Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 21:26:51 +0300 Subject: [PATCH 17/22] Add withdraw tests --- README.md | 13 ++++++ requests_test.go | 104 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 40c005b..689ffd7 100644 --- a/README.md +++ b/README.md @@ -122,5 +122,18 @@ and in handlers/verify_passport: // return nil, problems.BadRequest(err) // } ``` +and in handlers/withdraw(lines 49-58): +```go + // validated in requests.NewWithdraw + // addr, _ := cosmos.AccAddressFromBech32(req.Data.Attributes.Address) + // never panics because of request validation + proof.PubSignals[zk.Nullifier] = mustHexToInt(nullifier) + + // err = Verifier(r).VerifyProof(proof, zk.WithEventData(addr)) + // if err != nil { + // ape.RenderErr(w, problems.BadRequest(err)...) + // return + // } +``` Run service with config-testing.yaml (you need to configure db url) and run tests. \ No newline at end of file diff --git a/requests_test.go b/requests_test.go index dda9b97..562cdc6 100644 --- a/requests_test.go +++ b/requests_test.go @@ -45,8 +45,11 @@ const ( fraCode = "4608577" indCode = "4804164" mcoCode = "5063503" + belCode = "4343116" + mngCode = "5066311" genesisBalance = "0x0000000000000000000000000000000000000000000000000000000000000000" + rarimoAddress = "rarimo1h2077nfkksek386y8ks5m2wgd60wl3035n8gv0" balancesEndpoint = "public/balances" eventsEndpoint = "public/events" @@ -758,6 +761,95 @@ func TestReferralCodeStatuses(t *testing.T) { }) } +func TestWithdrawals(t *testing.T) { + t.Run("WithoutPassport", func(t *testing.T) { + n := nextN() + createAndValidateBalance(t, n, genesisCode) + _, err := withdraw(n, ukrCode, 10) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "400", apiErr.Status) + }) + + t.Run("BalanceAbsent", func(t *testing.T) { + n := nextN() + _, err := withdraw(n, ukrCode, 10) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "404", apiErr.Status) + }) + + t.Run("IncorrectCountryCode", func(t *testing.T) { + n := nextN() + createAndValidateBalance(t, n, genesisCode) + _, err := withdraw(n, "6974819", 10) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "500", apiErr.Status) + }) + + t.Run("CountryMismatched", func(t *testing.T) { + n := nextN() + createAndValidateBalance(t, n, genesisCode) + verifyPassport(n, ukrCode) + _, err := withdraw(n, fraCode, 1) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "400", apiErr.Status) + }) + + t.Run("InsufficientBalance", func(t *testing.T) { + n := nextN() + createAndValidateBalance(t, n, genesisCode) + verifyPassport(n, ukrCode) + _, err := withdraw(n, ukrCode, 10) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "400", apiErr.Status) + }) + + // TODO: Not enough level to do withdraw + + t.Run("WithdrawNotAllowed", func(t *testing.T) { + n := nextN() + createAndValidateBalance(t, n, genesisCode) + verifyPassport(n, belCode) + respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) + require.NoError(t, err) + require.Equal(t, 1, len(respEvents.Data)) + claimEventAndValidate(t, respEvents.Data[0].ID, n, 1) + _, err = withdraw(n, belCode, 4) + var apiErr *jsonapi.ErrorObject + require.ErrorAs(t, err, &apiErr) + require.Equal(t, "400", apiErr.Status) + }) +} + +func withdraw(nullifier, country string, amount int64) (resp resources.WithdrawalResponse, err error) { + proof := baseProof + proof.PubSignals[zk.Citizenship] = country + body := withdrawBody(nullifier, proof, amount) + err = requestWithBody(balancesEndpoint+"/"+nullifier+"/withdrawals", "POST", nullifier, body, &resp) + return + +} + +func withdrawBody(nullifier string, proof zkptypes.ZKProof, amount int64) resources.WithdrawRequest { + return resources.WithdrawRequest{ + Data: resources.Withdraw{ + Key: resources.Key{ + ID: nullifier, + Type: resources.WITHDRAW, + }, + Attributes: resources.WithdrawAttributes{ + Address: rarimoAddress, + Proof: proof, + Amount: amount, + }, + }, + } +} + func claimEvent(id, nullifier string) (resp resources.EventResponse, err error) { body := claimEventBody(id) err = requestWithBody(eventsEndpoint+"/"+id, "PATCH", nullifier, body, &resp) @@ -889,21 +981,21 @@ func doRequest(req *http.Request, user string, result any) error { log.Printf("Req: %s status=%d", reqLog, resp.StatusCode) + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read resp body: %w", err) + } + switch resp.StatusCode { case http.StatusOK, http.StatusCreated, http.StatusNoContent: default: - return &jsonapi.ErrorObject{Status: strconv.Itoa(resp.StatusCode)} + return &jsonapi.ErrorObject{Status: strconv.Itoa(resp.StatusCode), Title: string(respBody)} } if result == nil { return nil } - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read resp body: %w", err) - } - err = json.Unmarshal(respBody, result) if err != nil { return fmt.Errorf("failed to unmarshal response: %w", err) From 637e4ec34ad89c49c9b05f252bbedbc28a3ed818 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 21:27:23 +0300 Subject: [PATCH 18/22] Config for tests --- config-testing.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config-testing.yaml b/config-testing.yaml index e22629e..8fabbc6 100644 --- a/config-testing.yaml +++ b/config-testing.yaml @@ -105,6 +105,14 @@ countries: reserve_limit: 100 reserve_allowed: false withdrawal_allowed: false + - code: "BEL" + reserve_limit: 100 + reserve_allowed: true + withdrawal_allowed: false + - code: "MNG" + reserve_limit: 100 + reserve_allowed: false + withdrawal_allowed: true - code: "default" reserve_limit: 5 reserve_allowed: true From 58b604686b01a7cce1b9bb25b15d0938a80b008e Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 21:38:16 +0300 Subject: [PATCH 19/22] Fix docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 689ffd7..99e2070 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ and in handlers/withdraw(lines 49-58): // validated in requests.NewWithdraw // addr, _ := cosmos.AccAddressFromBech32(req.Data.Attributes.Address) // never panics because of request validation - proof.PubSignals[zk.Nullifier] = mustHexToInt(nullifier) + // proof.PubSignals[zk.Nullifier] = mustHexToInt(nullifier) // err = Verifier(r).VerifyProof(proof, zk.WithEventData(addr)) // if err != nil { From 8eaf8ae58179e9949e59746b00e370538f9f0fed Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 21:45:44 +0300 Subject: [PATCH 20/22] Comments refactoring --- requests_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/requests_test.go b/requests_test.go index 562cdc6..aa35ce8 100644 --- a/requests_test.go +++ b/requests_test.go @@ -162,7 +162,6 @@ func TestCreateBalance(t *testing.T) { otRefCode string ) - // fixme @violog: looks like fail on assert/require won't stop outer tests, must check before proceeding t.Run("BalanceGenesisCode", func(t *testing.T) { resp := createAndValidateBalance(t, nullifierShared, genesisCode) otRefCode = (*resp.Data.Attributes.ReferralCodes)[0].Id From 39e0e479c3e69d0ee0a025b4e926ca8698d2df9e Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 22:32:37 +0300 Subject: [PATCH 21/22] Errorhandling, tests refactoring --- requests_test.go | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/requests_test.go b/requests_test.go index aa35ce8..230e907 100644 --- a/requests_test.go +++ b/requests_test.go @@ -284,15 +284,6 @@ func TestVerifyPassport(t *testing.T) { assert.Equal(t, "500", apiErr.Status) getAndValidateBalance(t, n, false) }) - - t.Run("BlacklistedCountry", func(t *testing.T) { - n := nextN() - createAndValidateBalance(t, n, genesisCode) - resp, err := verifyPassport(n, usaCode) - require.NoError(t, err) - assert.False(t, resp.Data.Attributes.Claimed) - getAndValidateBalance(t, n, true) - }) } func getAndValidateBalance(t *testing.T, nullifier string, isVerified bool) resources.BalanceResponse { @@ -385,7 +376,7 @@ func TestEventsAutoClaim(t *testing.T) { }) // User can have a lot unclaimed fulfilled referral specific events if user not scan passport - t.Run("ReferralSpecificsAutoclaim", func(t *testing.T) { + t.Run("ReferralSpecificAutoclaimMany", func(t *testing.T) { n1, n2, n3 := nextN(), nextN(), nextN() respBalance, err := createBalance(n1, genesisCode) require.NoError(t, err) @@ -408,7 +399,7 @@ func TestEventsAutoClaim(t *testing.T) { fulfilledEventCount := 0 for _, event := range respEvents.Data { if event.Attributes.Status == string(data.EventFulfilled) { - fulfilledEventCount += 1 + fulfilledEventCount++ } } require.Equal(t, 2, fulfilledEventCount) @@ -423,7 +414,7 @@ func TestEventsAutoClaim(t *testing.T) { claimedEventCount := 0 for _, event := range respEvents.Data { if event.Attributes.Status == string(data.EventClaimed) { - claimedEventCount += 1 + claimedEventCount++ } } require.Equal(t, 2, claimedEventCount) @@ -669,7 +660,7 @@ func TestReferralCodeStatuses(t *testing.T) { require.False(t, respVerifyStatus.Data.Attributes.Claimed) refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id - _ = createAndValidateBalance(t, n2, refCode) + createAndValidateBalance(t, n2, refCode) respBalance = getAndValidateBalance(t, n1, true) for _, v := range *respBalance.Data.Attributes.ReferralCodes { @@ -688,7 +679,7 @@ func TestReferralCodeStatuses(t *testing.T) { require.False(t, respVerifyStatus.Data.Attributes.Claimed) refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id - _ = createAndValidateBalance(t, n2, refCode) + createAndValidateBalance(t, n2, refCode) respBalance = getAndValidateBalance(t, n1, true) for _, v := range *respBalance.Data.Attributes.ReferralCodes { @@ -704,7 +695,7 @@ func TestReferralCodeStatuses(t *testing.T) { respBalance := createAndValidateBalance(t, n1, genesisCode) refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id - _ = createAndValidateBalance(t, n2, refCode) + createAndValidateBalance(t, n2, refCode) respVerifyStatus, err := verifyPassport(n2, ukrCode) require.NoError(t, err) require.True(t, respVerifyStatus.Data.Attributes.Claimed) @@ -726,7 +717,7 @@ func TestReferralCodeStatuses(t *testing.T) { require.True(t, respVerifyStatus.Data.Attributes.Claimed) refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id - _ = createAndValidateBalance(t, n2, refCode) + createAndValidateBalance(t, n2, refCode) respVerifyStatus, err = verifyPassport(n2, ukrCode) require.NoError(t, err) require.True(t, respVerifyStatus.Data.Attributes.Claimed) @@ -748,7 +739,7 @@ func TestReferralCodeStatuses(t *testing.T) { require.True(t, respVerifyStatus.Data.Attributes.Claimed) refCode := (*respBalance.Data.Attributes.ReferralCodes)[0].Id - _ = createAndValidateBalance(t, n2, refCode) + createAndValidateBalance(t, n2, refCode) respBalance = getAndValidateBalance(t, n1, true) for _, v := range *respBalance.Data.Attributes.ReferralCodes { @@ -790,8 +781,9 @@ func TestWithdrawals(t *testing.T) { t.Run("CountryMismatched", func(t *testing.T) { n := nextN() createAndValidateBalance(t, n, genesisCode) - verifyPassport(n, ukrCode) - _, err := withdraw(n, fraCode, 1) + _, err := verifyPassport(n, ukrCode) + require.NoError(t, err) + _, err = withdraw(n, fraCode, 1) var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "400", apiErr.Status) @@ -800,8 +792,9 @@ func TestWithdrawals(t *testing.T) { t.Run("InsufficientBalance", func(t *testing.T) { n := nextN() createAndValidateBalance(t, n, genesisCode) - verifyPassport(n, ukrCode) - _, err := withdraw(n, ukrCode, 10) + _, err := verifyPassport(n, ukrCode) + require.NoError(t, err) + _, err = withdraw(n, ukrCode, 10) var apiErr *jsonapi.ErrorObject require.ErrorAs(t, err, &apiErr) require.Equal(t, "400", apiErr.Status) @@ -812,7 +805,8 @@ func TestWithdrawals(t *testing.T) { t.Run("WithdrawNotAllowed", func(t *testing.T) { n := nextN() createAndValidateBalance(t, n, genesisCode) - verifyPassport(n, belCode) + _, err := verifyPassport(n, belCode) + require.NoError(t, err) respEvents, err := getEvents(n, evtypes.TypeFreeWeekly) require.NoError(t, err) require.Equal(t, 1, len(respEvents.Data)) @@ -976,7 +970,7 @@ func doRequest(req *http.Request, user string, result any) error { if err != nil { return fmt.Errorf("failed to perform request (%s): %w", reqLog, err) } - defer func() { _ = resp.Body.Close() }() + defer func() { resp.Body.Close() }() log.Printf("Req: %s status=%d", reqLog, resp.StatusCode) From 5a4d0832faf785a3ed487f891b2f1e1705294d9f Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Thu, 20 Jun 2024 22:34:42 +0300 Subject: [PATCH 22/22] Remove unnecessary code --- requests_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/requests_test.go b/requests_test.go index 230e907..05fa3d5 100644 --- a/requests_test.go +++ b/requests_test.go @@ -891,9 +891,7 @@ type editReferralsResponse struct { func editReferrals(nullifier string, count uint64) (resp editReferralsResponse, err error) { req := requests.EditReferralsRequest{Nullifier: nullifier, Count: count} - fmt.Println(req) err = requestWithBody("private/referrals", "POST", "", req, &resp) - fmt.Println(resp) return }