package app import ( "database/sql" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/DATA-DOG/go-sqlmock" ) func TestTokenRoutes(t *testing.T) { s, mock, cleanup := newMockServer(t) defer cleanup() user := User{ID: 1, Email: "alice@example.com", Username: "alice"} token := "tok" now := time.Now().UTC() expectBearerUser(mock, token, user) mock.ExpectQuery("SELECT id, created_at, expires_at, revoked_at").WithArgs(user.ID).WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "expires_at", "revoked_at", "active"}).AddRow(int64(9), now, now.Add(time.Hour), sql.NullTime{}, true)) rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/tokens", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleTokenRoutes(rr, req, "") if rr.Code != http.StatusOK { t.Fatalf("list status=%d body=%s", rr.Code, rr.Body.String()) } var infos []TokenInfo if err := json.Unmarshal(rr.Body.Bytes(), &infos); err != nil { t.Fatal(err) } if len(infos) != 1 || infos[0].ID != 9 || !infos[0].Active { t.Fatalf("unexpected token infos: %#v", infos) } expectBearerUser(mock, token, user) mock.ExpectExec("UPDATE auth_tokens SET revoked_at").WithArgs(int64(9), user.ID).WillReturnResult(sqlmock.NewResult(0, 1)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodDelete, "/api/tokens/9", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleTokenRoutes(rr, req, "/9") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "revoked") { t.Fatalf("revoke status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, user) mock.ExpectExec("DELETE FROM auth_tokens WHERE user_id").WithArgs(user.ID).WillReturnResult(sqlmock.NewResult(0, 2)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodPost, "/api/tokens/prune", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleTokenRoutes(rr, req, "/prune") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "pruned") { t.Fatalf("prune status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, user) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodDelete, "/api/tokens/nope", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleTokenRoutes(rr, req, "/nope") if rr.Code != http.StatusBadRequest { t.Fatalf("invalid id status=%d body=%s", rr.Code, rr.Body.String()) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestAdminRoutes(t *testing.T) { s, mock, cleanup := newMockServer(t) defer cleanup() admin := User{ID: 1, Email: "admin@example.com", Username: "admin", IsAdmin: true} plain := User{ID: 2, Email: "bob@example.com", Username: "bob"} token := "tok" expectBearerUser(mock, token, plain) rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/admin/users", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleAdminRoutes(rr, req, "/users") if rr.Code != http.StatusForbidden { t.Fatalf("non-admin status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, admin) mock.ExpectQuery("SELECT id, email, username, is_admin FROM users").WillReturnRows(sqlmock.NewRows([]string{"id", "email", "username", "is_admin"}).AddRow(int64(1), "admin@example.com", "admin", true).AddRow(int64(2), "bob@example.com", "bob", false)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodGet, "/api/admin/users", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleAdminRoutes(rr, req, "/users") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "bob@example.com") { t.Fatalf("users status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, admin) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodDelete, "/api/admin/users/admin", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleAdminRoutes(rr, req, "/users/admin") if rr.Code != http.StatusBadRequest { t.Fatalf("delete self status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, admin) mock.ExpectExec("DELETE FROM users").WithArgs("bob").WillReturnResult(sqlmock.NewResult(0, 1)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodDelete, "/api/admin/users/bob", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleAdminRoutes(rr, req, "/users/bob") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "deleted") { t.Fatalf("delete status=%d body=%s", rr.Code, rr.Body.String()) } expectBearerUser(mock, token, admin) mock.ExpectExec("DELETE FROM auth_tokens WHERE expires_at").WillReturnResult(sqlmock.NewResult(0, 1)) rr = httptest.NewRecorder() req = httptest.NewRequest(http.MethodPost, "/api/admin/tokens/prune", nil) req.Header.Set("Authorization", "Bearer "+token) s.handleAdminRoutes(rr, req, "/tokens/prune") if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "pruned") { t.Fatalf("admin prune status=%d body=%s", rr.Code, rr.Body.String()) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } }