[GH-ISSUE #25] Phase 7: Sandbox tester management #2

Closed
opened 2026-02-26 21:32:41 +03:00 by kerem · 1 comment
Owner

Originally created by @rudrankriyam on GitHub (Jan 20, 2026).
Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/25

PRD: Sandbox Testers Management in ASC CLI

Overview

This PRD outlines the implementation of sandbox tester management for App Store Connect CLI, enabling developers and AI agents to create, list, and delete sandbox testers used for testing in-app purchases and subscriptions.

Goals

  1. Enable developers to create sandbox testers via CLI
  2. Support listing and filtering sandbox testers
  3. Provide deletion capabilities for sandbox testers
  4. Maintain AI-first design with JSON output by default
  5. Follow explicit, non-interactive CLI patterns
  6. Support clearing purchase history for testers (future enhancement)

API Endpoints

Core Sandbox Tester Endpoints

Method Endpoint Description
GET /v1/sandboxTesters List all sandbox testers
GET /v1/sandboxTesters/{id} Get specific sandbox tester details
POST /v1/sandboxTesters Create a new sandbox tester
DELETE /v1/sandboxTesters/{id} Delete a sandbox tester

Supporting Endpoints (Future)

Method Endpoint Description
POST /v1/sandboxTesters/{id}/clearPurchaseHistoryRequest Clear purchase history for a tester

Note: Some sources mention /v2/sandboxTesters, but /v1/sandboxTesters appears to be the standard endpoint. Implementation should verify the correct version during development.


CLI Commands

Command Structure

# List sandbox testers
asc sandbox list [flags]

# Create sandbox tester
asc sandbox create --email "tester@example.com" --first-name "John" --last-name "Doe" --password "SecurePass123!" --territory "USA" [flags]

# Get sandbox tester details
asc sandbox get --id "TESTER_ID" [flags]

# Delete sandbox tester
asc sandbox delete --id "TESTER_ID" [flags]
# OR
asc sandbox delete --email "tester@example.com" [flags]

Subcommands

list - List sandbox testers

Lists all sandbox testers for the team.

Flags:

  • --email (optional) - Filter by email address
  • --territory (optional) - Filter by territory code (e.g., "USA", "JPN", "GBR")
  • --limit (optional) - Maximum results per page (1-200)
  • --next (optional) - Fetch next page using links.next URL
  • --output (optional) - Output format: json (default), table, markdown
  • --pretty (optional) - Pretty-print JSON output

Examples:

# List all sandbox testers
asc sandbox list

# Filter by email
asc sandbox list --email "tester@example.com"

# Filter by territory
asc sandbox list --territory "USA"

# List with pagination
asc sandbox list --limit 50
asc sandbox list --next "<links.next>"

JSON Output:

{"data":[{"id":"tester-123","type":"sandboxTesters","attributes":{"firstName":"John","lastName":"Doe","email":"tester@example.com","territory":"USA"}}],"links":{"self":"...","next":null}}

create - Create sandbox tester

Creates a new sandbox tester account for testing in-app purchases.

Flags:

  • --email (required) - Email address (must be unique, not used for Apple ID)
  • --first-name (required) - First name
  • --last-name (required) - Last name
  • --password (required) - Password (8+ chars, uppercase, lowercase, number, symbol)
  • --territory (required) - Territory code (e.g., "USA", "JPN", "GBR")
  • --output (optional) - Output format: json (default), table, markdown
  • --pretty (optional) - Pretty-print JSON output

Examples:

# Create a sandbox tester
asc sandbox create --email "tester@example.com" --first-name "John" --last-name "Doe" --password "SecurePass123!" --territory "USA"

# Create tester for Japan
asc sandbox create --email "tester+ja@example.com" --first-name "太郎" --last-name "山田" --password "SecurePass123!" --territory "JPN"

JSON Output:

{"id":"tester-123","email":"tester@example.com","firstName":"John","lastName":"Doe","territory":"USA","createdDate":"2026-01-20T12:00:00Z"}

Note: Email subaddressing (e.g., user+test@example.com) is supported and recommended for creating multiple test accounts.

get - Get sandbox tester details

Gets detailed information about a specific sandbox tester.

Flags:

  • --id (required) - Sandbox tester ID (alternative to --email)
  • --email (optional) - Email address (alternative to --id)
  • --output (optional) - Output format: json (default), table, markdown
  • --pretty (optional) - Pretty-print JSON output

Examples:

# Get by ID
asc sandbox get --id "tester-123"

# Get by email
asc sandbox get --email "tester@example.com"

JSON Output:

{"id":"tester-123","email":"tester@example.com","firstName":"John","lastName":"Doe","territory":"USA","createdDate":"2026-01-20T12:00:00Z"}

delete - Delete sandbox tester

Permanently deletes a sandbox tester account.

Flags:

  • --id (required) - Sandbox tester ID (alternative to --email)
  • --email (optional) - Email address (alternative to --id)
  • --confirm (required) - Confirmation flag to prevent accidental deletions
  • --output (optional) - Output format: json (default), table, markdown
  • --pretty (optional) - Pretty-print JSON output

Examples:

# Delete by ID
asc sandbox delete --id "tester-123" --confirm

# Delete by email
asc sandbox delete --email "tester@example.com" --confirm

JSON Output:

{"id":"tester-123","email":"tester@example.com","deleted":true,"deletedDate":"2026-01-20T12:05:00Z"}

Note: If the tester is part of a "Sandbox Test Family," they must be removed from the family before deletion.


Request/Response Shapes

Create Sandbox Tester (POST)

Request:

{
  "data": {
    "type": "sandboxTesters",
    "attributes": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "tester@example.com",
      "password": "SecurePass123!",
      "territory": "USA"
    }
  }
}

Response:

{
  "data": {
    "type": "sandboxTesters",
    "id": "tester-123",
    "attributes": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "tester@example.com",
      "territory": "USA"
    }
  },
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/sandboxTesters/tester-123"
  }
}

Note: Password is not returned in the response for security reasons.

List Sandbox Testers (GET)

Request:

GET /v1/sandboxTesters?filter[email]=tester@example.com&filter[territory]=USA&limit=50

Response:

{
  "data": [
    {
      "type": "sandboxTesters",
      "id": "tester-123",
      "attributes": {
        "firstName": "John",
        "lastName": "Doe",
        "email": "tester@example.com",
        "territory": "USA"
      }
    }
  ],
  "links": {
    "self": "https://api.appstoreconnect.apple.com/v1/sandboxTesters?limit=50",
    "next": null
  }
}

Delete Sandbox Tester (DELETE)

Request:

DELETE /v1/sandboxTesters/tester-123

Response:

204 No Content

Required Fields

Create Sandbox Tester (POST)

Required Fields:

  • type: "sandboxTesters"
  • attributes.firstName: String - First name of the tester
  • attributes.lastName: String - Last name of the tester
  • attributes.email: String - Unique email address (not used for Apple ID)
  • attributes.password: String - Password meeting Apple's requirements:
    • Minimum 8 characters
    • At least one uppercase letter
    • At least one lowercase letter
    • At least one number
    • At least one special character
  • attributes.territory: String - Territory code (e.g., "USA", "JPN", "GBR", "CAN")

Optional Fields (may be required by API):

  • attributes.confirmPassword: String - Must match password (if required by API)
  • attributes.secretQuestion: String - Security question for account recovery
  • attributes.secretAnswer: String - Answer to security question
  • attributes.birthDate: String - Birth date in YYYY-MM-DD format

Note: Implementation should verify which optional fields are actually required by testing against the API.


Validation

Input Validation

  1. Email

    • Required for create and delete (when using --email)
    • Must be valid email format
    • Must be unique (not already used for Apple ID or sandbox account)
    • Subaddressing supported (e.g., user+test@example.com)
  2. First Name / Last Name

    • Required for create
    • Non-empty strings
    • No specific length limits (reasonable validation)
  3. Password

    • Required for create
    • Minimum 8 characters
    • Must contain uppercase, lowercase, number, and special character
    • Validation should occur before API call to provide immediate feedback
  4. Territory

    • Required for create
    • Must be valid territory code (e.g., "USA", "JPN", "GBR", "CAN")
    • Case-insensitive (normalize to uppercase)
    • Validate against known territory codes
  5. Tester ID

    • Required for get and delete (when using --id)
    • Must be valid UUID format (if API uses UUIDs)
  6. Confirmation Flag

    • --confirm required for delete
    • Prevents accidental deletions
  7. Limit

    • Must be between 1 and 200 if provided
    • Default: API default (typically 50)

API Validation

  1. Email Uniqueness

    • API will return error if email already exists
    • Handle 409 Conflict or similar error codes
  2. Territory Validity

    • API validates territory codes
    • Return clear error if invalid territory
  3. Password Strength

    • API validates password requirements
    • Return clear error with requirements if validation fails
  4. Tester Existence

    • Verify tester exists before deletion
    • Handle 404 Not Found errors
  5. Family Membership

    • Cannot delete tester if part of Sandbox Test Family
    • Return clear error with guidance

Error Handling

Common Errors

  1. Email Already Exists

    Error: email "tester@example.com" is already in use
    Hint: Use a different email or email subaddressing (e.g., user+test@example.com)
    
  2. Invalid Password

    Error: password does not meet requirements
    Requirements: 8+ characters, uppercase, lowercase, number, special character
    
  3. Invalid Territory

    Error: invalid territory code "INVALID"
    Valid codes: USA, JPN, GBR, CAN, etc.
    
  4. Tester Not Found

    Error: sandbox tester not found for email "tester@example.com"
    
  5. Tester Not Found (by ID)

    Error: sandbox tester not found for ID "tester-123"
    
  6. Missing Confirmation

    Error: --confirm is required to delete a sandbox tester
    
  7. Cannot Delete (Family Member)

    Error: cannot delete tester "tester-123" - tester is part of a Sandbox Test Family
    Hint: Remove tester from family first, then delete
    
  8. Unauthorized

    Error: unauthorized. Check your API credentials.
    Required roles: Account Holder, Admin, App Manager, or Developer
    
  9. Rate Limiting

    Error: rate limit exceeded. Please try again later.
    

Error Response Format

All errors follow the existing pattern:

  • Exit code: non-zero
  • Error message to stderr
  • JSON errors include structured details when available
{
  "error": {
    "code": "EMAIL_ALREADY_EXISTS",
    "message": "email is already in use",
    "details": {
      "email": "tester@example.com"
    }
  }
}

Testing Requirements

Unit Tests

Client Layer (internal/asc/client.go)

  1. GetSandboxTesters

    • Test query parameter building (filter[email], filter[territory], limit)
    • Test response parsing
    • Test error handling (404, 400, 403)
    • Test pagination (links.next)
  2. GetSandboxTester

    • Test by ID endpoint
    • Test response parsing
    • Test error handling (404)
  3. CreateSandboxTester

    • Test request body construction
    • Test required fields validation
    • Test response parsing
    • Test error handling (409 Conflict for duplicate email, 400 for invalid data)
  4. DeleteSandboxTester

    • Test DELETE endpoint
    • Test error handling (404, 403 if family member)

CLI Layer (cmd/sandbox.go)

  1. SandboxListCommand

    • Test flag validation
    • Test filter combinations (--email, --territory)
    • Test pagination (--next)
    • Test output formats (json, table, markdown)
  2. SandboxCreateCommand

    • Test flag validation (missing required flags)
    • Test password validation (before API call)
    • Test territory validation
    • Test email format validation
    • Test output formats
  3. SandboxGetCommand

    • Test flag validation (missing --id or --email)
    • Test ID vs email resolution
    • Test output formats
  4. SandboxDeleteCommand

    • Test flag validation (missing --confirm)
    • Test ID vs email resolution
    • Test confirmation requirement
    • Test output formats

Integration Tests (Opt-in)

Environment Variables:

  • ASC_SANDBOX_TEST_EMAIL - Email for testing (should use subaddressing)
  • ASC_SANDBOX_TEST_TERRITORY - Territory code for testing (default: "USA")
  • ASC_CONFIRM_DELETE=true - Required for actual deletions

Test Scenarios:

  1. List all sandbox testers
  2. Filter testers by email
  3. Filter testers by territory
  4. Create a new sandbox tester
  5. Get tester details by ID
  6. Get tester details by email
  7. Delete tester (if ASC_CONFIRM_DELETE=true)

Skip Conditions:

  • Skip if ASC_SANDBOX_TEST_EMAIL not set
  • Skip deletion tests if ASC_CONFIRM_DELETE not set to true
  • Skip if tester already exists (for create tests)

Implementation Checklist

Phase 1: Client Methods

  • GetSandboxTesters(ctx, opts...) - List testers with filters
  • GetSandboxTester(ctx, testerID) - Get tester by ID
  • CreateSandboxTester(ctx, email, firstName, lastName, password, territory) - Create tester
  • DeleteSandboxTester(ctx, testerID) - Delete tester
  • Add types for request/response structures
  • Add option functions for filters (WithSandboxTesterEmail, WithSandboxTesterTerritory, etc.)

Phase 2: CLI Commands

  • SandboxCommand - Parent command
  • SandboxListCommand - List testers
  • SandboxCreateCommand - Create tester
  • SandboxGetCommand - Get tester details
  • SandboxDeleteCommand - Delete tester
  • Add to RootCommand subcommands list

Phase 3: Validation

  • Email format validation
  • Password strength validation (before API call)
  • Territory code validation
  • Required field validation

Phase 4: Output Formatting

  • Table output for tester list
  • Table output for tester details
  • Markdown output for all commands
  • JSON output (minified by default)

Phase 5: Error Handling

  • Email already exists errors with hints
  • Password validation errors with requirements
  • Territory validation errors
  • Tester not found errors
  • Family membership errors

Phase 6: Tests

  • Unit tests for all client methods
  • Unit tests for all CLI commands
  • Integration tests (opt-in)
  • Error case tests

API Reference URLs

Official Documentation

  1. Sandbox Testing Overview

  2. Create Sandbox Account

  3. Manage Sandbox Account Settings

  4. App Store Connect API

  5. Testing In-App Purchases

OpenAPI Specification


Design Decisions

  1. Explicit Flags: All commands use long-form flags (--email, --first-name, --territory) following project conventions

  2. JSON-First: Default output is minified JSON for AI agent consumption

  3. Non-Interactive: No prompts; all required information via flags

  4. Confirmation Flags: --confirm required for destructive operations (delete)

  5. ID vs Email: Support both --id and --email for get/delete operations for flexibility

  6. Email Subaddressing: Explicitly support and recommend email subaddressing for multiple test accounts

  7. Password Validation: Validate password strength before API call to provide immediate feedback

  8. Territory Codes: Use standard territory codes (e.g., "USA", "JPN") matching API expectations

  9. Error Messages: Include actionable hints (e.g., "Use email subaddressing")

  10. Family Membership: Handle Sandbox Test Family constraints gracefully


Future Enhancements

  1. Clear Purchase History: Add asc sandbox clear-history --id "TESTER_ID" command

    • Uses /v1/sandboxTesters/{id}/clearPurchaseHistoryRequest endpoint
    • Allows resetting tester state for re-testing
  2. Bulk Operations: Create/delete multiple testers from a file

    • asc sandbox create-bulk --file "testers.json"
    • asc sandbox delete-bulk --file "testers.json"
  3. Tester Status: Show tester status (active, purchases, etc.)

    • asc sandbox status --id "TESTER_ID"
  4. Territory Management: List available territories

    • asc sandbox territories
  5. Password Generation: Auto-generate secure passwords

    • asc sandbox create --email "..." --auto-password

Notes

  • Sandbox testers are separate from TestFlight beta testers (managed via beta-testers command)
  • Maximum 10,000 sandbox accounts per team
  • Sandbox testers are used specifically for testing in-app purchases and subscriptions
  • Password cannot be retrieved after creation (not returned in API responses)
  • Email, name, and password cannot be edited after creation
  • Territory can potentially be updated (verify API support)
  • Some endpoints may require v2 API (/v2/sandboxTesters) - verify during implementation
  • Follow existing patterns from BetaTestersCommand for command structure
  • Maintain consistency with existing error handling and output formatting patterns

Territory Codes Reference

Common territory codes (verify against API):

  • USA - United States
  • JPN - Japan
  • GBR - United Kingdom
  • CAN - Canada
  • AUS - Australia
  • DEU - Germany
  • FRA - France
  • CHN - China
  • KOR - South Korea
  • BRA - Brazil

Note: Implementation should fetch or validate against the complete list of supported territories from the API or documentation.

Originally created by @rudrankriyam on GitHub (Jan 20, 2026). Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/25 # PRD: Sandbox Testers Management in ASC CLI ## Overview This PRD outlines the implementation of sandbox tester management for App Store Connect CLI, enabling developers and AI agents to create, list, and delete sandbox testers used for testing in-app purchases and subscriptions. ## Goals 1. Enable developers to create sandbox testers via CLI 2. Support listing and filtering sandbox testers 3. Provide deletion capabilities for sandbox testers 4. Maintain AI-first design with JSON output by default 5. Follow explicit, non-interactive CLI patterns 6. Support clearing purchase history for testers (future enhancement) --- ## API Endpoints ### Core Sandbox Tester Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/v1/sandboxTesters` | List all sandbox testers | | GET | `/v1/sandboxTesters/{id}` | Get specific sandbox tester details | | POST | `/v1/sandboxTesters` | Create a new sandbox tester | | DELETE | `/v1/sandboxTesters/{id}` | Delete a sandbox tester | ### Supporting Endpoints (Future) | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/v1/sandboxTesters/{id}/clearPurchaseHistoryRequest` | Clear purchase history for a tester | **Note:** Some sources mention `/v2/sandboxTesters`, but `/v1/sandboxTesters` appears to be the standard endpoint. Implementation should verify the correct version during development. --- ## CLI Commands ### Command Structure ```bash # List sandbox testers asc sandbox list [flags] # Create sandbox tester asc sandbox create --email "tester@example.com" --first-name "John" --last-name "Doe" --password "SecurePass123!" --territory "USA" [flags] # Get sandbox tester details asc sandbox get --id "TESTER_ID" [flags] # Delete sandbox tester asc sandbox delete --id "TESTER_ID" [flags] # OR asc sandbox delete --email "tester@example.com" [flags] ``` ### Subcommands #### `list` - List sandbox testers Lists all sandbox testers for the team. **Flags:** - `--email` (optional) - Filter by email address - `--territory` (optional) - Filter by territory code (e.g., "USA", "JPN", "GBR") - `--limit` (optional) - Maximum results per page (1-200) - `--next` (optional) - Fetch next page using `links.next` URL - `--output` (optional) - Output format: `json` (default), `table`, `markdown` - `--pretty` (optional) - Pretty-print JSON output **Examples:** ```bash # List all sandbox testers asc sandbox list # Filter by email asc sandbox list --email "tester@example.com" # Filter by territory asc sandbox list --territory "USA" # List with pagination asc sandbox list --limit 50 asc sandbox list --next "<links.next>" ``` **JSON Output:** ```json {"data":[{"id":"tester-123","type":"sandboxTesters","attributes":{"firstName":"John","lastName":"Doe","email":"tester@example.com","territory":"USA"}}],"links":{"self":"...","next":null}} ``` #### `create` - Create sandbox tester Creates a new sandbox tester account for testing in-app purchases. **Flags:** - `--email` (required) - Email address (must be unique, not used for Apple ID) - `--first-name` (required) - First name - `--last-name` (required) - Last name - `--password` (required) - Password (8+ chars, uppercase, lowercase, number, symbol) - `--territory` (required) - Territory code (e.g., "USA", "JPN", "GBR") - `--output` (optional) - Output format: `json` (default), `table`, `markdown` - `--pretty` (optional) - Pretty-print JSON output **Examples:** ```bash # Create a sandbox tester asc sandbox create --email "tester@example.com" --first-name "John" --last-name "Doe" --password "SecurePass123!" --territory "USA" # Create tester for Japan asc sandbox create --email "tester+ja@example.com" --first-name "太郎" --last-name "山田" --password "SecurePass123!" --territory "JPN" ``` **JSON Output:** ```json {"id":"tester-123","email":"tester@example.com","firstName":"John","lastName":"Doe","territory":"USA","createdDate":"2026-01-20T12:00:00Z"} ``` **Note:** Email subaddressing (e.g., `user+test@example.com`) is supported and recommended for creating multiple test accounts. #### `get` - Get sandbox tester details Gets detailed information about a specific sandbox tester. **Flags:** - `--id` (required) - Sandbox tester ID (alternative to `--email`) - `--email` (optional) - Email address (alternative to `--id`) - `--output` (optional) - Output format: `json` (default), `table`, `markdown` - `--pretty` (optional) - Pretty-print JSON output **Examples:** ```bash # Get by ID asc sandbox get --id "tester-123" # Get by email asc sandbox get --email "tester@example.com" ``` **JSON Output:** ```json {"id":"tester-123","email":"tester@example.com","firstName":"John","lastName":"Doe","territory":"USA","createdDate":"2026-01-20T12:00:00Z"} ``` #### `delete` - Delete sandbox tester Permanently deletes a sandbox tester account. **Flags:** - `--id` (required) - Sandbox tester ID (alternative to `--email`) - `--email` (optional) - Email address (alternative to `--id`) - `--confirm` (required) - Confirmation flag to prevent accidental deletions - `--output` (optional) - Output format: `json` (default), `table`, `markdown` - `--pretty` (optional) - Pretty-print JSON output **Examples:** ```bash # Delete by ID asc sandbox delete --id "tester-123" --confirm # Delete by email asc sandbox delete --email "tester@example.com" --confirm ``` **JSON Output:** ```json {"id":"tester-123","email":"tester@example.com","deleted":true,"deletedDate":"2026-01-20T12:05:00Z"} ``` **Note:** If the tester is part of a "Sandbox Test Family," they must be removed from the family before deletion. --- ## Request/Response Shapes ### Create Sandbox Tester (POST) **Request:** ```json { "data": { "type": "sandboxTesters", "attributes": { "firstName": "John", "lastName": "Doe", "email": "tester@example.com", "password": "SecurePass123!", "territory": "USA" } } } ``` **Response:** ```json { "data": { "type": "sandboxTesters", "id": "tester-123", "attributes": { "firstName": "John", "lastName": "Doe", "email": "tester@example.com", "territory": "USA" } }, "links": { "self": "https://api.appstoreconnect.apple.com/v1/sandboxTesters/tester-123" } } ``` **Note:** Password is not returned in the response for security reasons. ### List Sandbox Testers (GET) **Request:** ``` GET /v1/sandboxTesters?filter[email]=tester@example.com&filter[territory]=USA&limit=50 ``` **Response:** ```json { "data": [ { "type": "sandboxTesters", "id": "tester-123", "attributes": { "firstName": "John", "lastName": "Doe", "email": "tester@example.com", "territory": "USA" } } ], "links": { "self": "https://api.appstoreconnect.apple.com/v1/sandboxTesters?limit=50", "next": null } } ``` ### Delete Sandbox Tester (DELETE) **Request:** ``` DELETE /v1/sandboxTesters/tester-123 ``` **Response:** ``` 204 No Content ``` --- ## Required Fields ### Create Sandbox Tester (POST) **Required Fields:** - `type`: `"sandboxTesters"` - `attributes.firstName`: String - First name of the tester - `attributes.lastName`: String - Last name of the tester - `attributes.email`: String - Unique email address (not used for Apple ID) - `attributes.password`: String - Password meeting Apple's requirements: - Minimum 8 characters - At least one uppercase letter - At least one lowercase letter - At least one number - At least one special character - `attributes.territory`: String - Territory code (e.g., "USA", "JPN", "GBR", "CAN") **Optional Fields (may be required by API):** - `attributes.confirmPassword`: String - Must match `password` (if required by API) - `attributes.secretQuestion`: String - Security question for account recovery - `attributes.secretAnswer`: String - Answer to security question - `attributes.birthDate`: String - Birth date in `YYYY-MM-DD` format **Note:** Implementation should verify which optional fields are actually required by testing against the API. --- ## Validation ### Input Validation 1. **Email** - Required for `create` and `delete` (when using `--email`) - Must be valid email format - Must be unique (not already used for Apple ID or sandbox account) - Subaddressing supported (e.g., `user+test@example.com`) 2. **First Name / Last Name** - Required for `create` - Non-empty strings - No specific length limits (reasonable validation) 3. **Password** - Required for `create` - Minimum 8 characters - Must contain uppercase, lowercase, number, and special character - Validation should occur before API call to provide immediate feedback 4. **Territory** - Required for `create` - Must be valid territory code (e.g., "USA", "JPN", "GBR", "CAN") - Case-insensitive (normalize to uppercase) - Validate against known territory codes 5. **Tester ID** - Required for `get` and `delete` (when using `--id`) - Must be valid UUID format (if API uses UUIDs) 6. **Confirmation Flag** - `--confirm` required for `delete` - Prevents accidental deletions 7. **Limit** - Must be between 1 and 200 if provided - Default: API default (typically 50) ### API Validation 1. **Email Uniqueness** - API will return error if email already exists - Handle 409 Conflict or similar error codes 2. **Territory Validity** - API validates territory codes - Return clear error if invalid territory 3. **Password Strength** - API validates password requirements - Return clear error with requirements if validation fails 4. **Tester Existence** - Verify tester exists before deletion - Handle 404 Not Found errors 5. **Family Membership** - Cannot delete tester if part of Sandbox Test Family - Return clear error with guidance --- ## Error Handling ### Common Errors 1. **Email Already Exists** ``` Error: email "tester@example.com" is already in use Hint: Use a different email or email subaddressing (e.g., user+test@example.com) ``` 2. **Invalid Password** ``` Error: password does not meet requirements Requirements: 8+ characters, uppercase, lowercase, number, special character ``` 3. **Invalid Territory** ``` Error: invalid territory code "INVALID" Valid codes: USA, JPN, GBR, CAN, etc. ``` 4. **Tester Not Found** ``` Error: sandbox tester not found for email "tester@example.com" ``` 5. **Tester Not Found (by ID)** ``` Error: sandbox tester not found for ID "tester-123" ``` 6. **Missing Confirmation** ``` Error: --confirm is required to delete a sandbox tester ``` 7. **Cannot Delete (Family Member)** ``` Error: cannot delete tester "tester-123" - tester is part of a Sandbox Test Family Hint: Remove tester from family first, then delete ``` 8. **Unauthorized** ``` Error: unauthorized. Check your API credentials. Required roles: Account Holder, Admin, App Manager, or Developer ``` 9. **Rate Limiting** ``` Error: rate limit exceeded. Please try again later. ``` ### Error Response Format All errors follow the existing pattern: - Exit code: non-zero - Error message to stderr - JSON errors include structured details when available ```json { "error": { "code": "EMAIL_ALREADY_EXISTS", "message": "email is already in use", "details": { "email": "tester@example.com" } } } ``` --- ## Testing Requirements ### Unit Tests #### Client Layer (`internal/asc/client.go`) 1. **GetSandboxTesters** - Test query parameter building (`filter[email]`, `filter[territory]`, `limit`) - Test response parsing - Test error handling (404, 400, 403) - Test pagination (`links.next`) 2. **GetSandboxTester** - Test by ID endpoint - Test response parsing - Test error handling (404) 3. **CreateSandboxTester** - Test request body construction - Test required fields validation - Test response parsing - Test error handling (409 Conflict for duplicate email, 400 for invalid data) 4. **DeleteSandboxTester** - Test DELETE endpoint - Test error handling (404, 403 if family member) #### CLI Layer (`cmd/sandbox.go`) 1. **SandboxListCommand** - Test flag validation - Test filter combinations (`--email`, `--territory`) - Test pagination (`--next`) - Test output formats (json, table, markdown) 2. **SandboxCreateCommand** - Test flag validation (missing required flags) - Test password validation (before API call) - Test territory validation - Test email format validation - Test output formats 3. **SandboxGetCommand** - Test flag validation (missing `--id` or `--email`) - Test ID vs email resolution - Test output formats 4. **SandboxDeleteCommand** - Test flag validation (missing `--confirm`) - Test ID vs email resolution - Test confirmation requirement - Test output formats ### Integration Tests (Opt-in) **Environment Variables:** - `ASC_SANDBOX_TEST_EMAIL` - Email for testing (should use subaddressing) - `ASC_SANDBOX_TEST_TERRITORY` - Territory code for testing (default: "USA") - `ASC_CONFIRM_DELETE=true` - Required for actual deletions **Test Scenarios:** 1. List all sandbox testers 2. Filter testers by email 3. Filter testers by territory 4. Create a new sandbox tester 5. Get tester details by ID 6. Get tester details by email 7. Delete tester (if `ASC_CONFIRM_DELETE=true`) **Skip Conditions:** - Skip if `ASC_SANDBOX_TEST_EMAIL` not set - Skip deletion tests if `ASC_CONFIRM_DELETE` not set to `true` - Skip if tester already exists (for create tests) --- ## Implementation Checklist ### Phase 1: Client Methods - [ ] `GetSandboxTesters(ctx, opts...)` - List testers with filters - [ ] `GetSandboxTester(ctx, testerID)` - Get tester by ID - [ ] `CreateSandboxTester(ctx, email, firstName, lastName, password, territory)` - Create tester - [ ] `DeleteSandboxTester(ctx, testerID)` - Delete tester - [ ] Add types for request/response structures - [ ] Add option functions for filters (`WithSandboxTesterEmail`, `WithSandboxTesterTerritory`, etc.) ### Phase 2: CLI Commands - [ ] `SandboxCommand` - Parent command - [ ] `SandboxListCommand` - List testers - [ ] `SandboxCreateCommand` - Create tester - [ ] `SandboxGetCommand` - Get tester details - [ ] `SandboxDeleteCommand` - Delete tester - [ ] Add to `RootCommand` subcommands list ### Phase 3: Validation - [ ] Email format validation - [ ] Password strength validation (before API call) - [ ] Territory code validation - [ ] Required field validation ### Phase 4: Output Formatting - [ ] Table output for tester list - [ ] Table output for tester details - [ ] Markdown output for all commands - [ ] JSON output (minified by default) ### Phase 5: Error Handling - [ ] Email already exists errors with hints - [ ] Password validation errors with requirements - [ ] Territory validation errors - [ ] Tester not found errors - [ ] Family membership errors ### Phase 6: Tests - [ ] Unit tests for all client methods - [ ] Unit tests for all CLI commands - [ ] Integration tests (opt-in) - [ ] Error case tests --- ## API Reference URLs ### Official Documentation 1. **Sandbox Testing Overview** - https://developer.apple.com/help/app-store-connect/test-in-app-purchases/overview-of-testing-in-sandbox 2. **Create Sandbox Account** - https://developer.apple.com/help/app-store-connect/test-in-app-purchases/create-a-sandbox-apple-account 3. **Manage Sandbox Account Settings** - https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings 4. **App Store Connect API** - https://developer.apple.com/documentation/appstoreconnectapi 5. **Testing In-App Purchases** - https://developer.apple.com/documentation/storekit/testing-in-app-purchases-with-sandbox ### OpenAPI Specification - **Official Spec Download**: https://developer.apple.com/news/releases/ - **OpenAPI Mirror**: https://github.com/EvanBacon/App-Store-Connect-OpenAPI-Spec ### Related Documentation - **App Store Connect API Release Notes**: https://developer.apple.com/documentation/appstoreconnectapi/app-store-connect-api-release-notes - **App Store Connect API Authentication**: https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests --- ## Design Decisions 1. **Explicit Flags**: All commands use long-form flags (`--email`, `--first-name`, `--territory`) following project conventions 2. **JSON-First**: Default output is minified JSON for AI agent consumption 3. **Non-Interactive**: No prompts; all required information via flags 4. **Confirmation Flags**: `--confirm` required for destructive operations (delete) 5. **ID vs Email**: Support both `--id` and `--email` for get/delete operations for flexibility 6. **Email Subaddressing**: Explicitly support and recommend email subaddressing for multiple test accounts 7. **Password Validation**: Validate password strength before API call to provide immediate feedback 8. **Territory Codes**: Use standard territory codes (e.g., "USA", "JPN") matching API expectations 9. **Error Messages**: Include actionable hints (e.g., "Use email subaddressing") 10. **Family Membership**: Handle Sandbox Test Family constraints gracefully --- ## Future Enhancements 1. **Clear Purchase History**: Add `asc sandbox clear-history --id "TESTER_ID"` command - Uses `/v1/sandboxTesters/{id}/clearPurchaseHistoryRequest` endpoint - Allows resetting tester state for re-testing 2. **Bulk Operations**: Create/delete multiple testers from a file - `asc sandbox create-bulk --file "testers.json"` - `asc sandbox delete-bulk --file "testers.json"` 3. **Tester Status**: Show tester status (active, purchases, etc.) - `asc sandbox status --id "TESTER_ID"` 4. **Territory Management**: List available territories - `asc sandbox territories` 5. **Password Generation**: Auto-generate secure passwords - `asc sandbox create --email "..." --auto-password` --- ## Notes - Sandbox testers are separate from TestFlight beta testers (managed via `beta-testers` command) - Maximum 10,000 sandbox accounts per team - Sandbox testers are used specifically for testing in-app purchases and subscriptions - Password cannot be retrieved after creation (not returned in API responses) - Email, name, and password cannot be edited after creation - Territory can potentially be updated (verify API support) - Some endpoints may require v2 API (`/v2/sandboxTesters`) - verify during implementation - Follow existing patterns from `BetaTestersCommand` for command structure - Maintain consistency with existing error handling and output formatting patterns --- ## Territory Codes Reference Common territory codes (verify against API): - `USA` - United States - `JPN` - Japan - `GBR` - United Kingdom - `CAN` - Canada - `AUS` - Australia - `DEU` - Germany - `FRA` - France - `CHN` - China - `KOR` - South Korea - `BRA` - Brazil **Note:** Implementation should fetch or validate against the complete list of supported territories from the API or documentation.
kerem closed this issue 2026-02-26 21:32:42 +03:00
Author
Owner

@rudrankriyam commented on GitHub (Jan 21, 2026):

Completed via PR #35 and released in 0.3.0. Closing as done.

<!-- gh-comment-id:3776950861 --> @rudrankriyam commented on GitHub (Jan 21, 2026): Completed via PR #35 and released in 0.3.0. Closing as done.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/App-Store-Connect-CLI#2
No description provided.