Skip to content

Add company forms, signatories, and federal-tax read commands - #150

Open
ashieh wants to merge 2 commits into
mainfrom
company-forms-reads
Open

Add company forms, signatories, and federal-tax read commands#150
ashieh wants to merge 2 commits into
mainfrom
company-forms-reads

Conversation

@ashieh

@ashieh ashieh commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

What

Adds first-class read commands under company for data that was previously only reachable through the raw gusto api escape hatch:

Command Endpoint
company forms list GET /v1/companies/{uuid}/forms
company forms show <form_uuid> (alias get) GET /v1/forms/{uuid}
company forms pdf <form_uuid> GET /v1/forms/{uuid}/pdf
company signatories GET /v1/companies/{uuid}/signatories
company federal-taxes GET /v1/companies/{uuid}/federal_tax_details

forms is a nested sub-group (list/show/pdf) since it has multiple operations; signatories and federal-taxes are single-op and stay flat, mirroring company locations.

Scopes

Adds the matching read scopes to required-scopes.ts: company_forms:read, signatories:read, company_federal_taxes:read. These are read-only and do not overlap with the dropped write/manage scopes.

Notes

  • The two collection reads (forms list, signatories) guard against a non-array 2xx body with malformed_response, matching the existing collection-read convention. federal-taxes returns a single object, so it has no array guard.
  • All uuids are percent-encoded into a single path segment.

Testing

  • Unit tests for all five handlers, including the malformed-body path for the two lists.
  • required-scopes test asserts the three new scopes are present and not dropped.
  • Smoke tests assert each command dispatches (reaches the auth check) in the compiled binary.
  • Verified live against the demo environment: all five commands return real data (2 forms with a working PDF link, 1 signatory, populated federal tax details).

@ashieh
ashieh requested a review from a team as a code owner July 24, 2026 17:09
@ashieh
ashieh requested review from DamoneMX and tmfahey July 24, 2026 17:15
Beta users could only reach these via the raw `gusto api` escape hatch.
Surface them as first-class read commands under `company`, add the
matching read scopes to required-scopes, and cover them with unit,
scope, and smoke tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Signed-off-by: Austin Shieh <austin.shieh@gusto.com>
@ashieh
ashieh force-pushed the company-forms-reads branch from cd021c0 to 9ce9286 Compare July 24, 2026 19:33
Comment thread src/commands/company.ts Outdated
tokenStdin?: boolean;
}

export function companyFormsListHandler(opts: CompanyResourceReadOpts): CommandHandler {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forms list and signatories hit fetchCompanyResource with no --cursor/--limit/--all — every other company-scoped collection endpoint here (contractor list, employee list) goes through parsePaginationFlags + ctx.client.paginate. If a company's got more forms or signatories than fit in one page, this silently returns page one with no next and no signal anything's missing. company locations skips pagination too, but that's a naturally small list — forms can pile up over years. Worth wiring the same pagination flags in, or confirming with the API that these two never paginate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 96ac7c4. forms list now goes through parsePaginationFlags + ctx.client.paginate with --cursor/--limit/--all (matching employee/contractor list) and surfaces next. I confirmed with the API: the company forms index paginates server-side (its controller calls paginate(...)), so a single un-paged GET was a real silent-truncation risk. Signatories I left un-paginated on purpose: that endpoint does not paginate and the API caps a company at one signatory, so there is no page to drop.

});
});

describe("companyFormsListHandler", () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the five new handlers gets a non-2xx test — this file only covers the happy path plus the malformed-body case for the two array endpoints. company.network.test.ts and employee.network.test.ts both test the 404/500 → ApiError → exit code mapping per handler; this is the first batch of new handlers in a while that skips it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 96ac7c4. Each of the five new handlers now has a non-2xx spec asserting the ApiError maps to a failed result with the api-client exit code (ExitCode.ApiClient). I used 404 rather than 500 because a persistent 500 trips the client's real retry backoff and hangs the test; 404 exercises the same ApiError to exit-code path. The forms-list case also gained a --limit cap test and a --cursor + --all conflict test.

Comment thread src/commands/company.ts
};
}

export function companyFormShowHandler(formUuid: string, opts: FormReadOpts): CommandHandler {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

companyFormShowHandler/companyFormPdfHandler call fetchResource(...) untyped, so result.data stays unknown. fetchResource<T> already supports a type param and companyShowHandler above uses it (ctx.client.get<CompanyRecord>) — worth typing these two the same way.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 96ac7c4. Both now pass the type param: fetchResource<FormRecord> for show and fetchResource<FormPdfUrl> for pdf, so result.data is typed instead of unknown. The field shapes come from the forms presenter (title/type/year/quarter/requires_signing/document_url, etc.).

forms list now uses parsePaginationFlags + client.paginate (--cursor/--limit/--all) like employee and contractor list; the forms index paginates server-side, so the prior single un-paged GET silently dropped later pages. signatories stays un-paginated because the API caps a company at one signatory.

Type companyFormShowHandler and companyFormPdfHandler fetchResource calls (FormRecord, FormPdfUrl) so result.data is no longer unknown, and add non-2xx specs for all five new company read handlers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Austin Shieh <austin.shieh@gusto.com>
@ashieh
ashieh requested a review from tmfahey July 29, 2026 16:12
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.

2 participants