package client
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func newTestServer(t *testing.T, mux *http.ServeMux) *httptest.Server {
t.Helper()
ts := httptest.NewServer(mux)
t.Cleanup(ts.Close)
return ts
}
func writeJSON(t *testing.T, w http.ResponseWriter, v any) {
t.Helper()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(v); err != nil {
t.Fatalf("failed to write JSON response: %v", err)
}
}
func setupOAuthMux(t *testing.T, mux *http.ServeMux) {
t.Helper()
mux.HandleFunc("/oauth/token", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, TokenResponse{
AccessToken: "test-token",
ExpiresIn: 3600,
TokenType: "Bearer",
})
})
}
func newTestClient(t *testing.T, mux *http.ServeMux) *Client {
t.Helper()
setupOAuthMux(t, mux)
ts := newTestServer(t, mux)
c, err := New(context.Background(), "test-id", "test-secret", ts.URL)
if err != nil {
t.Fatalf("unexpected error creating client: %v", err)
}
return c
}
func TestGetUsers(t *testing.T) {
mux := http.NewServeMux()
now := time.Now().UTC().Truncate(time.Second)
testUsers := []User{
{ID: "u1", Email: "alice@test.com", FirstName: "Alice", LastName: "Smith", Username: "alice", Status: "active", CreatedAt: now},
{ID: "u2", Email: "bob@test.com", FirstName: "Bob", LastName: "Jones", Username: "bob", Status: "disabled", CreatedAt: now},
}
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
cursor := r.URL.Query().Get("cursor")
if cursor == "" {
writeJSON(t, w, ListResponse[User]{Data: testUsers[:1], NextCursor: "page2"})
return
}
writeJSON(t, w, ListResponse[User]{Data: testUsers[1:], NextCursor: ""})
})
c := newTestClient(t, mux)
t.Run("first page", func(t *testing.T) {
users, next, err := c.GetUsers(context.Background(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(users) != 1 {
t.Fatalf("expected 1 user, got %d", len(users))
}
if users[0].ID != "u1" {
t.Errorf("expected user ID u1, got %s", users[0].ID)
}
if next != "page2" {
t.Errorf("expected next cursor page2, got %s", next)
}
})
t.Run("second page", func(t *testing.T) {
users, next, err := c.GetUsers(context.Background(), "page2")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(users) != 1 {
t.Fatalf("expected 1 user, got %d", len(users))
}
if users[0].ID != "u2" {
t.Errorf("expected user ID u2, got %s", users[0].ID)
}
if next != "" {
t.Errorf("expected empty next cursor, got %s", next)
}
})
}
func TestGetRoles(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/roles", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, ListResponse[Role]{
Data: []Role{
{ID: "r1", Name: "Single Ride", Description: "One-time journey on any standard route"},
{ID: "r2", Name: "Standard Day", Description: "Unlimited standard travel for the day"},
},
})
})
c := newTestClient(t, mux)
roles, next, err := c.GetRoles(context.Background(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(roles) != 2 {
t.Fatalf("expected 2 roles, got %d", len(roles))
}
if roles[0].Name != "Single Ride" {
t.Errorf("expected Single Ride, got %s", roles[0].Name)
}
if next != "" {
t.Errorf("expected empty next cursor, got %s", next)
}
}
func TestGetGroups(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/groups", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, ListResponse[Group]{
Data: []Group{{ID: "g1", Name: "Regional Pass", Description: "Monthly pass for standard daily travel"}},
})
})
c := newTestClient(t, mux)
groups, next, err := c.GetGroups(context.Background(), "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(groups) != 1 {
t.Fatalf("expected 1 group, got %d", len(groups))
}
if groups[0].Name != "Regional Pass" {
t.Errorf("expected Regional Pass, got %s", groups[0].Name)
}
if next != "" {
t.Errorf("expected empty next cursor, got %s", next)
}
}
func TestGetRoleMembers(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/roles/r1/members", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, ListResponse[Member]{Data: []Member{{UserID: "u1"}, {UserID: "u2"}}})
})
c := newTestClient(t, mux)
members, next, err := c.GetRoleMembers(context.Background(), "r1", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(members) != 2 {
t.Fatalf("expected 2 members, got %d", len(members))
}
if next != "" {
t.Errorf("expected empty next cursor, got %s", next)
}
}
func TestGetGroupMembers(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/groups/g1/members", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, ListResponse[Member]{Data: []Member{{UserID: "u1"}}})
})
c := newTestClient(t, mux)
members, next, err := c.GetGroupMembers(context.Background(), "g1", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(members) != 1 {
t.Fatalf("expected 1 member, got %d", len(members))
}
if members[0].UserID != "u1" {
t.Errorf("expected user ID u1, got %s", members[0].UserID)
}
if next != "" {
t.Errorf("expected empty next cursor, got %s", next)
}
}
func TestCreateUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(SingleResponse[User]{
Data: User{
ID: "u-new", Email: req.Email,
FirstName: req.FirstName, LastName: req.LastName,
Username: req.Username, Status: "active",
CreatedAt: time.Now().UTC(),
},
}); err != nil {
t.Errorf("failed to encode response: %v", err)
}
})
c := newTestClient(t, mux)
user, err := c.CreateUser(context.Background(), &CreateUserRequest{
Email: "new@test.com", FirstName: "New", LastName: "User", Username: "newuser",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.ID != "u-new" {
t.Errorf("expected user ID u-new, got %s", user.ID)
}
if user.Email != "new@test.com" {
t.Errorf("expected email new@test.com, got %s", user.Email)
}
}
func TestAddRoleMember(t *testing.T) {
mux := http.NewServeMux()
var calledPath string
var calledMethod string
mux.HandleFunc("/api/roles/r1/members/u1", func(w http.ResponseWriter, r *http.Request) {
calledPath = r.URL.Path
calledMethod = r.Method
w.WriteHeader(http.StatusNoContent)
})
c := newTestClient(t, mux)
if err := c.AddRoleMember(context.Background(), "r1", "u1"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if calledMethod != http.MethodPut {
t.Errorf("expected PUT, got %s", calledMethod)
}
if calledPath != "/api/roles/r1/members/u1" {
t.Errorf("unexpected path: %s", calledPath)
}
}
func TestRemoveRoleMember(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/roles/r1/members/u1", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusNoContent)
})
c := newTestClient(t, mux)
if err := c.RemoveRoleMember(context.Background(), "r1", "u1"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestAddGroupMember(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/groups/g1/members/u1", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
c := newTestClient(t, mux)
if err := c.AddGroupMember(context.Background(), "g1", "u1"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestRemoveGroupMember(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/groups/g1/members/u1", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusNoContent)
})
c := newTestClient(t, mux)
if err := c.RemoveGroupMember(context.Background(), "g1", "u1"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGetCurrentUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/users/me", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, SingleResponse[User]{
Data: User{
ID: "u-me", Email: "me@test.com",
FirstName: "Test", LastName: "User",
Username: "testuser", Status: "active",
CreatedAt: time.Now().UTC(),
},
})
})
c := newTestClient(t, mux)
user, err := c.GetCurrentUser(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.ID != "u-me" {
t.Errorf("expected user ID u-me, got %s", user.ID)
}
}
func TestUpdateUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPatch {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var attrs map[string]string
if err := json.NewDecoder(r.Body).Decode(&attrs); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
status := "active"
if v, ok := attrs["status"]; ok {
status = v
}
dept := ""
if v, ok := attrs["department"]; ok {
dept = v
}
writeJSON(t, w, SingleResponse[User]{
Data: User{
ID: "u1", Email: "alice@test.com",
FirstName: "Alice", LastName: "Smith",
Username: "alice", Status: status,
Department: dept,
},
})
})
c := newTestClient(t, mux)
t.Run("update status", func(t *testing.T) {
user, err := c.UpdateUser(context.Background(), "u1", map[string]string{"status": "inactive"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Status != "inactive" {
t.Errorf("expected status inactive, got %s", user.Status)
}
})
t.Run("update department", func(t *testing.T) {
user, err := c.UpdateUser(context.Background(), "u1", map[string]string{"department": "Engineering"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Department != "Engineering" {
t.Errorf("expected department Engineering, got %s", user.Department)
}
})
}
func TestGetUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, SingleResponse[User]{
Data: User{
ID: "u1", Email: "alice@test.com",
FirstName: "Alice", LastName: "Smith",
Username: "alice", Status: "active",
},
})
})
c := newTestClient(t, mux)
user, err := c.GetUser(context.Background(), "u1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.ID != "u1" {
t.Errorf("expected user ID u1, got %s", user.ID)
}
if user.Email != "alice@test.com" {
t.Errorf("expected email alice@test.com, got %s", user.Email)
}
}
func TestGetRole(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/roles/", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, SingleResponse[Role]{
Data: Role{ID: "r1", Name: "Single Ride", Description: "One-time journey on any standard route"},
})
})
c := newTestClient(t, mux)
role, err := c.GetRole(context.Background(), "r1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if role.ID != "r1" {
t.Errorf("expected role ID r1, got %s", role.ID)
}
if role.Name != "Single Ride" {
t.Errorf("expected role name Single Ride, got %s", role.Name)
}
}
func TestGetGroup(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/groups/", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, SingleResponse[Group]{
Data: Group{ID: "g1", Name: "Regional Pass", Description: "Monthly pass for standard daily travel"},
})
})
c := newTestClient(t, mux)
group, err := c.GetGroup(context.Background(), "g1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if group.ID != "g1" {
t.Errorf("expected group ID g1, got %s", group.ID)
}
if group.Name != "Regional Pass" {
t.Errorf("expected group name Regional Pass, got %s", group.Name)
}
}
func TestGetRoleGroups(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/roles/r2/groups", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, ListResponse[GroupAssignment]{
Data: []GroupAssignment{{GroupID: "g1"}},
})
})
c := newTestClient(t, mux)
groups, err := c.GetRoleGroups(context.Background(), "r2")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(groups) != 1 {
t.Fatalf("expected 1 group assignment, got %d", len(groups))
}
if groups[0].GroupID != "g1" {
t.Errorf("expected group ID g1, got %s", groups[0].GroupID)
}
}
func TestGetRoleRoles(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/api/roles/r3/roles", func(w http.ResponseWriter, r *http.Request) {
writeJSON(t, w, ListResponse[RoleAssignment]{
Data: []RoleAssignment{{RoleID: "r4"}},
})
})
c := newTestClient(t, mux)
roles, err := c.GetRoleRoles(context.Background(), "r3")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(roles) != 1 {
t.Fatalf("expected 1 role assignment, got %d", len(roles))
}
if roles[0].RoleID != "r4" {
t.Errorf("expected role ID r4, got %s", roles[0].RoleID)
}
}