Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions sdk/go/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package scaledtest

import (
"context"
"net/url"
)

// AdminService exposes the /api/v1/admin endpoints (owner role required).
type AdminService struct {
client *Client
}

// ListUsersParams filters the users listing.
type ListUsersParams struct {
Limit int
Offset int
}

// ListUsers returns all users (admin-only).
func (s *AdminService) ListUsers(ctx context.Context, p *ListUsersParams) (*ListUsersResponse, error) {
var q url.Values
if p != nil {
q = addInt(q, "limit", p.Limit)
q = addInt(q, "offset", p.Offset)
}
var out ListUsersResponse
if err := s.client.doAPI(ctx, "GET", "/admin/users", q, nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// ListAuditLogParams filters the audit log query.
type ListAuditLogParams struct {
Action string
ResourceType string
ActorID string
Since string // RFC3339
Until string // RFC3339
Limit int
Offset int
}

// ListAuditLog returns audit log entries (admin-only).
func (s *AdminService) ListAuditLog(ctx context.Context, p *ListAuditLogParams) (*ListAuditLogResponse, error) {
var q url.Values
if p != nil {
q = addQuery(q, "action", p.Action)
q = addQuery(q, "resource_type", p.ResourceType)
q = addQuery(q, "actor_id", p.ActorID)
q = addQuery(q, "since", p.Since)
q = addQuery(q, "until", p.Until)
q = addInt(q, "limit", p.Limit)
q = addInt(q, "offset", p.Offset)
}
var out ListAuditLogResponse
if err := s.client.doAPI(ctx, "GET", "/admin/audit-log", q, nil, &out); err != nil {
return nil, err
}
return &out, nil
}
97 changes: 97 additions & 0 deletions sdk/go/analytics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package scaledtest

import (
"context"
"net/url"
)

// AnalyticsService exposes the /api/v1/analytics endpoints.
type AnalyticsService struct {
client *Client
}

// TrendsParams filters the trends query.
type TrendsParams struct {
Start string // RFC3339
End string // RFC3339
GroupBy string // "day", "week", "month"
}

// GetTrends returns pass/fail trends over time.
func (s *AnalyticsService) GetTrends(ctx context.Context, p *TrendsParams) (*TrendsResponse, error) {
var q url.Values
if p != nil {
q = addQuery(q, "start", p.Start)
q = addQuery(q, "end", p.End)
q = addQuery(q, "group_by", p.GroupBy)
}
var out TrendsResponse
if err := s.client.doAPI(ctx, "GET", "/analytics/trends", q, nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// FlakyTestsParams filters the flaky-tests query.
type FlakyTestsParams struct {
WindowDays int
MinRuns int
Limit int
}

// GetFlakyTests returns tests detected as flaky.
func (s *AnalyticsService) GetFlakyTests(ctx context.Context, p *FlakyTestsParams) (*FlakyTestsResponse, error) {
var q url.Values
if p != nil {
q = addInt(q, "window_days", p.WindowDays)
q = addInt(q, "min_runs", p.MinRuns)
q = addInt(q, "limit", p.Limit)
}
var out FlakyTestsResponse
if err := s.client.doAPI(ctx, "GET", "/analytics/flaky-tests", q, nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// ErrorAnalysisParams filters the error-analysis query.
type ErrorAnalysisParams struct {
Start string // RFC3339
End string // RFC3339
Limit int
}

// GetErrorAnalysis returns clusters of similar error messages.
func (s *AnalyticsService) GetErrorAnalysis(ctx context.Context, p *ErrorAnalysisParams) (*ErrorAnalysisResponse, error) {
var q url.Values
if p != nil {
q = addQuery(q, "start", p.Start)
q = addQuery(q, "end", p.End)
q = addInt(q, "limit", p.Limit)
}
var out ErrorAnalysisResponse
if err := s.client.doAPI(ctx, "GET", "/analytics/error-analysis", q, nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// DurationDistributionParams filters the duration-distribution query.
type DurationDistributionParams struct {
Start string // RFC3339
End string // RFC3339
}

// GetDurationDistribution returns a histogram of test durations.
func (s *AnalyticsService) GetDurationDistribution(ctx context.Context, p *DurationDistributionParams) (*DurationDistributionResponse, error) {
var q url.Values
if p != nil {
q = addQuery(q, "start", p.Start)
q = addQuery(q, "end", p.End)
}
var out DurationDistributionResponse
if err := s.client.doAPI(ctx, "GET", "/analytics/duration-distribution", q, nil, &out); err != nil {
return nil, err
}
return &out, nil
}
106 changes: 106 additions & 0 deletions sdk/go/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package scaledtest

import (
"context"
"errors"
)

// AuthService exposes the /auth and /api/v1/auth endpoints.
//
// The ScaledTest refresh-token flow uses an HttpOnly cookie set by the
// server. Callers that need refresh should configure a cookie jar on the
// underlying *http.Client (via WithHTTPClient) so the refresh_token cookie
// is persisted across requests. For non-browser callers that cannot use a
// cookie jar, use the Auth.RefreshWithToken helper which sends the refresh
// token explicitly via the Authorization header (the ScaledTest server
// accepts a bearer refresh token on /auth/refresh as a fallback).
type AuthService struct {
client *Client
}

// Register creates a new user account.
func (s *AuthService) Register(ctx context.Context, req *RegisterRequest) (*AuthResponse, error) {
if req == nil || req.Email == "" || req.Password == "" || req.DisplayName == "" {
return nil, errors.New("scaledtest: email, password, and display_name are required")
}
var out AuthResponse
if err := s.client.doRaw(ctx, "POST", "/auth/register", nil, req, &out); err != nil {
return nil, err
}
return &out, nil
}

// Login authenticates an existing user.
func (s *AuthService) Login(ctx context.Context, req *LoginRequest) (*AuthResponse, error) {
if req == nil || req.Email == "" || req.Password == "" {
return nil, errors.New("scaledtest: email and password are required")
}
var out AuthResponse
if err := s.client.doRaw(ctx, "POST", "/auth/login", nil, req, &out); err != nil {
return nil, err
}
return &out, nil
}

// Refresh exchanges a refresh token for a new access token. The ScaledTest
// server reads the refresh token from the refresh_token cookie; callers using
// a cookie jar can call Refresh and rely on the jar. Callers without a cookie
// jar should use RefreshWithToken.
func (s *AuthService) Refresh(ctx context.Context) (*RefreshTokenResponse, error) {
var out RefreshTokenResponse
if err := s.client.doRaw(ctx, "POST", "/auth/refresh", nil, nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// RefreshWithToken sends the given refresh token as a Bearer header on the
// /auth/refresh request. This is the non-browser fallback for callers that
// do not maintain a cookie jar. The refresh token is sent only for this
// single request; the client's configured access token is not touched, so
// concurrent requests on the same Client are unaffected.
func (s *AuthService) RefreshWithToken(ctx context.Context, refreshToken string) (*RefreshTokenResponse, error) {
if refreshToken == "" {
return nil, errors.New("scaledtest: refresh token is required")
}
var out RefreshTokenResponse
if err := s.client.doRawWithToken(ctx, "POST", "/auth/refresh", nil, nil, &out, refreshToken); err != nil {
return nil, err
}
return &out, nil
}

// GetMe returns the authenticated user's profile.
func (s *AuthService) GetMe(ctx context.Context) (*UserProfile, error) {
var out UserProfile
if err := s.client.doAPI(ctx, "GET", "/auth/me", nil, nil, &out); err != nil {
return nil, err
}
return &out, nil
}

// UpdateProfile updates the authenticated user's display name.
func (s *AuthService) UpdateProfile(ctx context.Context, displayName string) (*UserProfile, error) {
if displayName == "" {
return nil, errors.New("scaledtest: display_name is required")
}
body := UpdateProfileRequest{DisplayName: displayName}
var out UserProfile
if err := s.client.doAPI(ctx, "PATCH", "/auth/me", nil, body, &out); err != nil {
return nil, err
}
return &out, nil
}

// ChangePassword changes the authenticated user's password.
func (s *AuthService) ChangePassword(ctx context.Context, currentPassword, newPassword string) (*ChangePasswordResponse, error) {
if currentPassword == "" || newPassword == "" {
return nil, errors.New("scaledtest: current_password and new_password are required")
}
body := ChangePasswordRequest{CurrentPassword: currentPassword, NewPassword: newPassword}
var out ChangePasswordResponse
if err := s.client.doAPI(ctx, "POST", "/auth/change-password", nil, body, &out); err != nil {
return nil, err
}
return &out, nil
}
Loading
Loading