package handlers_test import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // tokenRouter builds a chi router with token + app API routes. func tokenRouter(tc *testContext) http.Handler { r := chi.NewRouter() r.Route("/api/v1", func(apiR chi.Router) { apiR.Post("/login", tc.handlers.HandleAPILoginPOST()) apiR.Group(func(apiR chi.Router) { apiR.Use(tc.middleware.APISessionAuth()) apiR.Get("/whoami", tc.handlers.HandleAPIWhoAmI()) apiR.Post("/tokens", tc.handlers.HandleAPICreateToken()) apiR.Get("/tokens", tc.handlers.HandleAPIListTokens()) apiR.Delete( "/tokens/{tokenID}", tc.handlers.HandleAPIDeleteToken(), ) apiR.Get("/apps", tc.handlers.HandleAPIListApps()) }) }) return r } func TestAPICreateToken(t *testing.T) { t.Parallel() tc, cookies := setupAPITest(t) r := tokenRouter(tc) body := `{"name":"my-ci-token"}` req := httptest.NewRequest( http.MethodPost, "/api/v1/tokens", strings.NewReader(body), ) req.Header.Set("Content-Type", "application/json") for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusCreated, rr.Code) var resp map[string]any require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) assert.Equal(t, "my-ci-token", resp["name"]) assert.Contains(t, resp["token"], "upaas_") assert.NotEmpty(t, resp["id"]) } func TestAPICreateTokenMissingName(t *testing.T) { t.Parallel() tc, cookies := setupAPITest(t) r := tokenRouter(tc) body := `{"name":""}` req := httptest.NewRequest( http.MethodPost, "/api/v1/tokens", strings.NewReader(body), ) req.Header.Set("Content-Type", "application/json") for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) } func TestAPIListTokens(t *testing.T) { t.Parallel() tc, cookies := setupAPITest(t) r := tokenRouter(tc) // Create two tokens. for _, name := range []string{"token-a", "token-b"} { body := `{"name":"` + name + `"}` req := httptest.NewRequest( http.MethodPost, "/api/v1/tokens", strings.NewReader(body), ) req.Header.Set("Content-Type", "application/json") for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) require.Equal(t, http.StatusCreated, rr.Code) } // List tokens. req := httptest.NewRequest(http.MethodGet, "/api/v1/tokens", nil) for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var tokens []map[string]any require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &tokens)) assert.Len(t, tokens, 2) // Plaintext token must NOT appear in list. for _, tok := range tokens { assert.Nil(t, tok["token"]) } } func TestAPIDeleteToken(t *testing.T) { t.Parallel() tc, cookies := setupAPITest(t) r := tokenRouter(tc) // Create a token. body := `{"name":"delete-me"}` req := httptest.NewRequest( http.MethodPost, "/api/v1/tokens", strings.NewReader(body), ) req.Header.Set("Content-Type", "application/json") for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) tokenID, ok := created["id"].(string) require.True(t, ok) // Delete it. req = httptest.NewRequest( http.MethodDelete, "/api/v1/tokens/"+tokenID, nil, ) for _, c := range cookies { req.AddCookie(c) } rr = httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) // List should be empty. req = httptest.NewRequest(http.MethodGet, "/api/v1/tokens", nil) for _, c := range cookies { req.AddCookie(c) } rr = httptest.NewRecorder() r.ServeHTTP(rr, req) var tokens []map[string]any require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &tokens)) assert.Empty(t, tokens) } func TestAPIBearerTokenAuth(t *testing.T) { t.Parallel() tc, cookies := setupAPITest(t) r := tokenRouter(tc) // Create a token via session auth. body := `{"name":"bearer-test"}` req := httptest.NewRequest( http.MethodPost, "/api/v1/tokens", strings.NewReader(body), ) req.Header.Set("Content-Type", "application/json") for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) plaintext, ok := created["token"].(string) require.True(t, ok) // Use Bearer token to access an authenticated endpoint. req = httptest.NewRequest(http.MethodGet, "/api/v1/apps", nil) req.Header.Set("Authorization", "Bearer "+plaintext) rr = httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) } func TestAPIBearerTokenInvalid(t *testing.T) { t.Parallel() tc := setupTestHandlers(t) r := tokenRouter(tc) req := httptest.NewRequest(http.MethodGet, "/api/v1/apps", nil) req.Header.Set("Authorization", "Bearer upaas_invalidtoken1234567890ab") rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusUnauthorized, rr.Code) } func TestAPIBearerTokenRevoked(t *testing.T) { t.Parallel() tc, cookies := setupAPITest(t) r := tokenRouter(tc) // Create then delete a token. body := `{"name":"revoke-test"}` req := httptest.NewRequest( http.MethodPost, "/api/v1/tokens", strings.NewReader(body), ) req.Header.Set("Content-Type", "application/json") for _, c := range cookies { req.AddCookie(c) } rr := httptest.NewRecorder() r.ServeHTTP(rr, req) require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) plaintext, ok := created["token"].(string) require.True(t, ok) tokenID, ok := created["id"].(string) require.True(t, ok) // Delete (revoke) the token. req = httptest.NewRequest( http.MethodDelete, "/api/v1/tokens/"+tokenID, nil, ) for _, c := range cookies { req.AddCookie(c) } rr = httptest.NewRecorder() r.ServeHTTP(rr, req) require.Equal(t, http.StatusOK, rr.Code) // Try to use the revoked token. req = httptest.NewRequest(http.MethodGet, "/api/v1/apps", nil) req.Header.Set("Authorization", "Bearer "+plaintext) rr = httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusUnauthorized, rr.Code) }