Skip to content

Phase 11: Metadata API (thin wrapper)#26

Merged
rianjs merged 1 commit intomainfrom
feat/21-metadata-api-thin-wrapper
Feb 1, 2026
Merged

Phase 11: Metadata API (thin wrapper)#26
rianjs merged 1 commit intomainfrom
feat/21-metadata-api-thin-wrapper

Conversation

@rianjs
Copy link
Contributor

@rianjs rianjs commented Feb 1, 2026

Summary

  • Add Metadata API client (api/metadata/) for basic metadata operations
  • Add sfdc metadata commands: types, list, retrieve, deploy
  • Thin wrapper approach - for complex workflows, recommend using official sf CLI

New Commands

List Available Types

sfdc metadata types
sfdc metadata types -o json

List Components

sfdc metadata list --type ApexClass
sfdc metadata list --type ApexTrigger
sfdc metadata list --type ApexPage -o json

Retrieve Metadata

sfdc metadata retrieve --type ApexClass --name MyController --output ./src
sfdc metadata retrieve --type ApexClass --output ./src  # all classes

Deploy Metadata

sfdc metadata deploy --source ./src
sfdc metadata deploy --source ./src --check-only
sfdc metadata deploy --source ./src --test-level RunLocalTests --wait

Supported Types for Direct Operations

  • ApexClass, ApexTrigger, ApexPage, ApexComponent

Design Notes

  • Uses REST-based Tooling API for queries (consistent with Phase 10)
  • Uses Metadata REST API for deploy operations
  • Zip utilities for package creation/extraction
  • Intentionally limited scope - complex operations should use sf CLI

Test plan

  • All existing tests pass
  • New Metadata API client tests pass
  • New command tests pass
  • Linter passes

Closes #21

🤖 Generated with Claude Code

Add basic Metadata API support for developer convenience:

- api/metadata: New package for Metadata API operations using REST endpoints
  - DescribeMetadata: List available metadata types
  - ListMetadata: List components of a type
  - Retrieve: Get component source code
  - Deploy: Deploy metadata from local directory
  - Zip utilities for package creation/extraction

- sfdc metadata commands:
  - types: List available metadata types
  - list: List components of a type
  - retrieve: Retrieve components to local directory
  - deploy: Deploy from local directory with --check-only and --wait

Supported types for direct operations: ApexClass, ApexTrigger, ApexPage, ApexComponent

For complex workflows, users should use the official sf CLI.

Closes #21
@rianjs
Copy link
Contributor Author

rianjs commented Feb 1, 2026

Test Coverage Assessment

I've reviewed the test coverage for this PR adding Metadata API support.

Summary

Overall, the test coverage is solid for the core functionality. The PR adds ~1900 lines of code and ~800 lines of tests across two test files (api/metadata/client_test.go and internal/cmd/metadatacmd/metadata_test.go).

What's Well Covered

API Client (api/metadata/client.go)

  • Client initialization with validation (missing instance URL, missing HTTP client)
  • DescribeMetadata - happy path with filtering logic
  • ListMetadata - happy path with record parsing
  • ListMetadata - unsupported type error handling
  • Retrieve - happy path (ApexClass with Body field)
  • Retrieve - not found error handling
  • Deploy - request construction and initial response
  • GetDeployStatus - status polling response
  • CreateZipFromDirectory - zip creation with subdirectories
  • ExtractZipToDirectory - extraction with nested paths
  • Security: Zip slip vulnerability protection (critical!)
  • API error handling (401 status codes)

Commands (internal/cmd/metadatacmd/)

  • types command - table and JSON output
  • list command - happy path with component count
  • list command - missing --type flag validation
  • retrieve command - single component retrieval
  • retrieve command - missing flags validation
  • deploy command - basic deployment flow
  • deploy command - check-only flag behavior
  • deploy command - missing --source flag validation
  • getFileExtension utility function

Areas Without Direct Tests (but acceptable)

  1. Retrieve for non-Body types (ApexPage/ApexComponent with Markup field) - The code path is covered by the same test infrastructure, just with different JSON response parsing.

  2. RetrieveAll - Not directly tested in isolation, but the retrieve command test exercises this through the command flow. The silently-skip-errors-and-continue pattern for individual component failures is consistent with other CLIs and reasonable for bulk operations.

  3. deploy --wait polling loop - The test starts a deployment but doesn't test the polling behavior. This is pragmatic since polling loops with time.Sleep are tricky to test without flakiness.

  4. displayDeployResult edge cases - Failure details display (component failures with line numbers, test errors) aren't tested. These are output formatting details that don't involve business logic.

  5. ListMetadata for all supported types - Tests only ApexClass, but the switch statement covers 7 types. The pattern is identical, so this is acceptable.

Potential Gaps to Consider (Minor)

  1. Custom API version handling - The client accepts cfg.APIVersion but there's no test verifying custom versions flow through to the URL.

  2. Instance URL trailing slash normalization - Code trims trailing slashes but no test validates this.

  3. Retrieve with no content - When a record exists but has neither Body nor Markup, the error path ("no content found") isn't tested.

Verdict

The test coverage is adequate for merge. The critical paths are tested:

  • All API methods have happy-path tests
  • Input validation is tested for all commands
  • Security-sensitive code (zip slip) is tested
  • Error conditions are tested for the most important scenarios

The untested paths are primarily:

  • Output formatting variations (low risk)
  • Edge cases in bulk operations that silently continue (intentional design)
  • Long-running polling behaviors (hard to test reliably)

This follows the principle of testing critical business logic while being pragmatic about diminishing returns on edge case coverage.

@rianjs rianjs merged commit 66a5c45 into main Feb 1, 2026
3 checks passed
@rianjs rianjs deleted the feat/21-metadata-api-thin-wrapper branch February 1, 2026 12:01
@rianjs rianjs mentioned this pull request Feb 2, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 11: Metadata API (thin wrapper)

1 participant

Comments