package handler import ( "bytes" "context" "encoding/json" "fmt" "git.infra.nkode.tech/dkelly/nkode-core/api" "git.infra.nkode.tech/dkelly/nkode-core/email" "git.infra.nkode.tech/dkelly/nkode-core/entities" "git.infra.nkode.tech/dkelly/nkode-core/models" "git.infra.nkode.tech/dkelly/nkode-core/repository" "git.infra.nkode.tech/dkelly/nkode-core/security" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "log" "net/http" "net/http/httptest" "os" "testing" ) func TestNKodeAPI(t *testing.T) { tr := NewTestRouter() tr.Start() defer func(tr *TestRouter) { err := tr.Stop() assert.NoError(t, err) }(tr) // *** Create New Customer with invalid access token *** customerID, err := tr.CreateNewCustomerDefaultPolicy() assert.NoError(t, err) attrPerKey := 9 numKeys := 6 userEmail := "test_username" + security.GenerateNonSecureRandomString(12) + "@example.com" // *** Signup *** resp, status, err := tr.Signup(customerID, attrPerKey, numKeys, userEmail) assert.NoError(t, err) assert.Equal(t, 200, status) passcodeLen := 4 userPasscode := resp.UserIdxInterface[:passcodeLen] kpSet := entities.KeypadDimension{ AttrsPerKey: numKeys, NumbOfKeys: numKeys, } setKeySelection, err := entities.SelectKeyByAttrIdx(resp.UserIdxInterface, userPasscode, kpSet) assert.NoError(t, err) // *** Set nKode *** confirmInterface, status, err := tr.SetNKode(customerID, setKeySelection, resp.SessionID) assert.NoError(t, err) assert.Equal(t, 200, status) confirmKeySelection, err := entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet) assert.NoError(t, err) // *** Confirm nKode *** status, err = tr.ConfirmNKode(customerID, confirmKeySelection, resp.SessionID) assert.NoError(t, err) assert.Equal(t, 200, status) // *** Get Login Interface *** loginInterface, status, err := tr.GetLoginInterface(userEmail, customerID) assert.NoError(t, err) assert.Equal(t, 200, status) kp := entities.KeypadDimension{ AttrsPerKey: attrPerKey, NumbOfKeys: numKeys, } loginKeySelection, err := entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, kp) assert.NoError(t, err) // *** Login *** tokens, status, err := tr.Login(customerID, userEmail, loginKeySelection) assert.NoError(t, err) assert.Equal(t, 200, status) assert.NotEmpty(t, tokens.AccessToken) assert.NotEmpty(t, tokens.RefreshToken) // *** Renew Attributes *** status, err = tr.RenewAttributes(customerID) assert.NoError(t, err) assert.Equal(t, 200, status) loginInterface, status, err = tr.GetLoginInterface(userEmail, customerID) assert.NoError(t, err) loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, kp) assert.NoError(t, err) tokens, status, err = tr.Login(customerID, userEmail, loginKeySelection) assert.NoError(t, err) // *** Test Forgot nKode *** status, err = tr.ForgotNKode(customerID, userEmail) assert.NoError(t, err) assert.Equal(t, 200, status) // *** Test Reset nKode *** nkodeResetJwt, err := security.ResetNKodeToken(userEmail, customerID) assert.NoError(t, err) resetResp, status, err := tr.Reset(customerID, attrPerKey, numKeys, userEmail, nkodeResetJwt) assert.NoError(t, err) assert.Equal(t, 200, status) assert.NotEmpty(t, resetResp.SessionID) userPasscode = resetResp.UserIdxInterface[:passcodeLen] setKeySelection, err = entities.SelectKeyByAttrIdx(resetResp.UserIdxInterface, userPasscode, kpSet) assert.NoError(t, err) confirmInterface, status, err = tr.SetNKode(customerID, setKeySelection, resetResp.SessionID) assert.NoError(t, err) assert.Equal(t, 200, status) confirmKeySelection, err = entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet) assert.NoError(t, err) status, err = tr.ConfirmNKode(customerID, confirmKeySelection, resetResp.SessionID) assert.NoError(t, err) assert.Equal(t, 200, status) loginInterface, status, err = tr.GetLoginInterface(userEmail, customerID) assert.NoError(t, err) assert.Equal(t, 200, status) loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, kp) assert.NoError(t, err) tokens, status, err = tr.Login(customerID, userEmail, loginKeySelection) assert.NoError(t, err) assert.Equal(t, 200, status) assert.NotEmpty(t, tokens.AccessToken) assert.NotEmpty(t, tokens.RefreshToken) // *** Test Reset nKode with invalid token *** _, status, err = tr.Reset(customerID, attrPerKey, numKeys, userEmail, "invalid token") assert.Error(t, err) assert.Equal(t, 403, status) } type TestRouter struct { router *gin.Engine emailQueue *email.Queue repo *repository.SqliteNKodeRepo handler *NkodeHandler } func NewTestRouter() *TestRouter { gin.SetMode(gin.TestMode) router := gin.Default() logger := log.Default() ctx := context.Background() dbPath := os.Getenv("TEST_DB") repo, err := repository.NewSqliteNKodeRepo(ctx, dbPath) if err != nil { log.Fatal(err) } emailClient := email.TestEmailClient{} emailQueue := email.NewEmailQueue(email.EmailQueueBufferSize, email.MaxEmailsPerSecond, &emailClient) nkodeAPI := api.NewNKodeAPI(repo, emailQueue) h := NkodeHandler{ API: nkodeAPI, Logger: logger, } h.RegisterRoutes(router) return &TestRouter{ handler: &h, router: router, emailQueue: emailQueue, repo: repo, } } func (r *TestRouter) Start() { r.repo.Start() r.emailQueue.Start() } func (r *TestRouter) Stop() error { r.emailQueue.Stop() return r.repo.Stop() } func (r *TestRouter) CreateNewCustomerDefaultPolicy() (string, error) { customerId, err := r.handler.API.CreateNewCustomer(entities.NewDefaultNKodePolicy()) if err != nil { return "", err } return customerId.String(), nil } func (r *TestRouter) Signup( customerID string, attrsPerKey int, numberOfKeys int, userEmail string, ) (*entities.SignupResetInterface, int, error) { body := bytes.NewBufferString(fmt.Sprintf( "customer_id=%s&attrs_per_key=%d&numb_of_keys=%d&email=%s", customerID, attrsPerKey, numberOfKeys, userEmail, )) req := httptest.NewRequest(http.MethodPost, "/signup", body) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) var resp entities.SignupResetInterface if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { return nil, rec.Code, err } return &resp, rec.Code, nil } func (r *TestRouter) SetNKode( customerID string, selection []int, sessionID string, ) ([]int, int, error) { data := models.SetNKodePost{ CustomerID: customerID, KeySelection: selection, SessionID: sessionID, } body, err := json.Marshal(data) if err != nil { return nil, 0, err } req := httptest.NewRequest(http.MethodPost, "/set-nkode", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) var resp struct { UserInterface []int `json:"user_interface"` } if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { return nil, rec.Code, err } return resp.UserInterface, rec.Code, nil } func (r *TestRouter) ConfirmNKode( customerID string, selection entities.KeySelection, sessionID string, ) (int, error) { data := models.ConfirmNKodePost{ CustomerID: customerID, KeySelection: selection, SessionID: sessionID, } body, err := json.Marshal(data) if err != nil { return 0, err } req := httptest.NewRequest(http.MethodPost, "/confirm-nkode", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) return rec.Code, nil } func (r *TestRouter) GetLoginInterface( userEmail string, customerID string, ) (entities.LoginInterface, int, error) { body := bytes.NewBufferString(fmt.Sprintf( "email=%s&customer_id=%s", userEmail, customerID, )) req := httptest.NewRequest(http.MethodPost, "/get-login-interface", body) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) var resp entities.LoginInterface if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { return entities.LoginInterface{}, rec.Code, err } return resp, rec.Code, nil } func (r *TestRouter) Login( customerID string, userEmail string, selection []int, ) (security.AuthenticationTokens, int, error) { data := models.LoginPost{ CustomerID: customerID, UserEmail: userEmail, KeySelection: selection, } body, err := json.Marshal(data) if err != nil { return security.AuthenticationTokens{}, 0, err } req := httptest.NewRequest(http.MethodPost, "/login", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) var resp security.AuthenticationTokens if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { return security.AuthenticationTokens{}, rec.Code, err } return resp, rec.Code, nil } func (r *TestRouter) RenewAttributes( customerID string, ) (int, error) { data := models.RenewAttributesPost{ CustomerID: customerID, } body, err := json.Marshal(data) if err != nil { return 0, err } req := httptest.NewRequest(http.MethodPost, "/renew-attributes", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) return rec.Code, nil } func (r *TestRouter) ForgotNKode( customerID string, userEmail string, ) (int, error) { data := models.ForgotNKodePost{ CustomerID: customerID, UserEmail: userEmail, } body, err := json.Marshal(data) if err != nil { return 0, err } req := httptest.NewRequest(http.MethodPost, "/forgot-nkode", bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) return rec.Code, nil } func (r *TestRouter) Reset( customerID string, attrsPerKey int, numberOfKeys int, userEmail string, resetAuthToken string, ) (*entities.SignupResetInterface, int, error) { body := bytes.NewBufferString(fmt.Sprintf( "customer_id=%s&attrs_per_key=%d&numb_of_keys=%d&email=%s", customerID, attrsPerKey, numberOfKeys, userEmail, )) req := httptest.NewRequest(http.MethodPost, "/reset-nkode", body) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Authorization", "Bearer "+resetAuthToken) rec := httptest.NewRecorder() r.router.ServeHTTP(rec, req) var resp entities.SignupResetInterface if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { return nil, rec.Code, err } return &resp, rec.Code, nil }