Skip to content

feat: add TenantInstanceFetchCommand and fix dedicated URL resolution#184

Merged
jbiskur merged 3 commits intomainfrom
feature/tenant-instance-fetch-command
Mar 4, 2026
Merged

feat: add TenantInstanceFetchCommand and fix dedicated URL resolution#184
jbiskur merged 3 commits intomainfrom
feature/tenant-instance-fetch-command

Conversation

@jbiskur
Copy link
Contributor

@jbiskur jbiskur commented Mar 4, 2026

Summary

  • Add TenantInstanceFetchCommand using the new /instance endpoint (by-name/{name}/instance and by-id/{id}/instance) that requires only authentication, no IAM validation
  • Switch getDedicatedBaseUrl() to use the new command, eliminating the 403 catch that silently cached isDedicated: false
  • Make configurationRepoCredentials nullable in TenantSchema and TenantFetchCommand response schema

Test plan

  • New tests for TenantInstanceFetchCommand: by-name, by-id, non-dedicated, and 404 cases
  • Updated dedicated subdomain tests in command.test.ts to verify /instance endpoint
  • Updated data-core deletion tests to use new instance endpoint mocks
  • All 167 test steps pass (deno test -A)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Fetch tenant instances by ID or tenant name; tenant-instance retrieval is now public.
    • TenantInstance type added to the public API.
  • Improvements

    • Tenant instance shape simplified with direct domain access and more flexible status typing.
    • Configuration repository credentials now accept null where applicable.
  • Bug Fixes

    • 404 for missing tenant instance returns a clear Not Found error.
  • Tests

    • Added coverage for tenant-instance fetch (by name, by id, non-dedicated, and 404 cases).

Use new /instance endpoint for dedicated URL resolution instead of
TenantFetchCommand, avoiding 403 errors from IAM validation.
Also make configurationRepoCredentials nullable in TenantSchema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jbiskur jbiskur requested a review from suuunly March 4, 2026 12:30
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a359207-c913-47a6-bfee-033f41037d0f

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba55c2 and b42610b.

📒 Files selected for processing (1)
  • test/tests/commands/tenant.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/tests/commands/tenant.test.ts

📝 Walkthrough

Walkthrough

This PR adds TenantInstance fetch support: new TenantInstanceFetchCommand and TenantInstance schema/type, migrates tenant lookups to /instance endpoints, flattens tenant shape (dedicatedinstance), allows configurationRepoCredentials to be string | null, and updates call sites and tests to the new shapes.

Changes

Cohort / File(s) Summary
New Tenant Instance Fetch Command
src/commands/index.ts, src/commands/tenant/tenant-instance.fetch.ts
Adds TenantInstanceFetchCommand (fetch by id or name), exports it publicly, parses responses with TenantInstanceSchema, and maps 404 → NotFoundException.
Contract and Schema Updates
src/contracts/tenant.ts, src/contracts/index.ts
Adds TenantInstanceSchema / TenantInstance type; updates TenantSchema to allow configurationRepoCredentials to be `string
Core Command Logic Migration
src/common/command.ts, src/common/tenant.cache.ts
Replaces previous tenant fetch with TenantInstanceFetchCommand; changes tenant shape from dedicated.configuration.domain to instance.domain; renames/relaxes cache typing (dedicatedinstance, status becomes string).
Tenant fetch schema tweak
src/commands/tenant/tenant.fetch.ts
Adjusts runtime/static schema to allow configurationRepoCredentials to be `string
Tests and mocks updated
test/tests/commands/tenant.test.ts, test/tests/commands/data-core.test.ts, test/tests/common/command.test.ts
Updates tests and HTTP mocks to use /api/v1/tenants/.../instance endpoints and { instance: ... } response shape; adds by-id/by-name, non-dedicated, and 404 behavior tests; removes old Tenant imports where applicable.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Command as TenantInstanceFetchCommand
    participant API as Tenant API
    participant Parser as Response Parser
    participant Handler as Error Handler

    Client->>Command: execute(input: by-id | by-name)
    Command->>Command: getMethod() -> "GET"
    Command->>Command: getPath() -> /api/v1/tenants/by-id/{id}/instance or /api/v1/tenants/by-name/{name}/instance
    Command->>API: GET request
    alt 200 OK
        API-->>Command: { instance: { status, domain } | null }
        Command->>Parser: parseResponse(rawResponse)
        Parser-->>Command: TenantInstance
        Command-->>Client: return TenantInstance
    else 404 Not Found
        API-->>Command: 404
        Command->>Handler: handleClientError(404)
        Handler-->>Client: throw NotFoundException (id or name)
    else Other Error
        API-->>Command: error
        Command->>Handler: handleClientError(error)
        Handler-->>Client: rethrow error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • suuunly

Poem

🐰 I hopped to fetch an instance bright,
By-name or id, I raced through night,
If missing, I’ll shout NotFound with flair,
Domain now flat — breeze in my hair,
Hooray for instances everywhere! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding TenantInstanceFetchCommand and fixing dedicated URL resolution via the /instance endpoint.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/tenant-instance-fetch-command

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/tests/commands/tenant.test.ts (1)

83-97: Add a by-id 404 test for parity with the by-name error path.

You already verify 404 for name lookup (Line 114). Adding the same assertion for id lookup would harden NotFoundException mapping for { id: ... }.

Suggested test addition
+  it("should throw NotFoundException for missing tenant instance by id", async () => {
+    const tenantId = crypto.randomUUID()
+    fetchMockerBuilder.get(`/api/v1/tenants/by-id/${tenantId}/instance`)
+      .respondWith(404, { message: "Tenant not found" })
+
+    const command = new TenantInstanceFetchCommand({ tenantId })
+
+    await assertRejects(
+      () => flowcoreClient.execute(command),
+      NotFoundException,
+      `Tenant not found: ${JSON.stringify({ id: tenantId })}`,
+    )
+  })

Also applies to: 114-125

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tests/commands/tenant.test.ts` around lines 83 - 97, Add a parallel 404
test for the id lookup to match the existing by-name error path: mock
fetchMockerBuilder.get(`/api/v1/tenants/by-id/${tenantId}/instance`) to
respondWith(404) and assert that executing new TenantInstanceFetchCommand({
tenantId }) via flowcoreClient rejects/throws the same NotFoundException mapping
you already verify for the name path; update the test file so the id-based
failure mirrors the behavior asserted in the by-name 404 test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/tenant/tenant-instance.fetch.ts`:
- Around line 54-57: The routing and error-filter branches use a truthy check on
this.input.tenantId which fails for valid falsy strings; change the guards to
use a discriminant presence check (e.g. if ("tenantId" in this.input) rather
than if ("tenantId" in this.input && this.input.tenantId")) in the
tenant-instance.fetch code paths (the branch that returns
`/api/v1/tenants/by-id/${this.input.tenantId}/instance`) and update the
corresponding error/filter logic that currently checks this.input.tenantId
truthiness to also use the `"tenantId" in this.input` presence check so empty or
falsy string IDs route and produce metadata correctly.

In `@src/contracts/tenant.ts`:
- Around line 151-160: Add an explicit type annotation to the exported
TenantInstanceSchema to match the other schemas; change the declaration of
TenantInstanceSchema so it is typed as TObject<{ isDedicated: boolean; instance:
{ status: string; domain: string } | null }> (i.e., use the same TObject<...>
generic pattern used by TenantSchema/TenantListItemSchema/TenantUserSchema)
while keeping the existing Type.Object(...) initializer and the symbol name
TenantInstanceSchema.

---

Nitpick comments:
In `@test/tests/commands/tenant.test.ts`:
- Around line 83-97: Add a parallel 404 test for the id lookup to match the
existing by-name error path: mock
fetchMockerBuilder.get(`/api/v1/tenants/by-id/${tenantId}/instance`) to
respondWith(404) and assert that executing new TenantInstanceFetchCommand({
tenantId }) via flowcoreClient rejects/throws the same NotFoundException mapping
you already verify for the name path; update the test file so the id-based
failure mirrors the behavior asserted in the by-name 404 test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90554789-6653-4998-b59a-1b0d96c65f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 225ac43 and f87338c.

📒 Files selected for processing (10)
  • src/commands/index.ts
  • src/commands/tenant/tenant-instance.fetch.ts
  • src/commands/tenant/tenant.fetch.ts
  • src/common/command.ts
  • src/common/tenant.cache.ts
  • src/contracts/index.ts
  • src/contracts/tenant.ts
  • test/tests/commands/data-core.test.ts
  • test/tests/commands/tenant.test.ts
  • test/tests/common/command.test.ts

Comment on lines +54 to +57
if ("tenantId" in this.input && this.input.tenantId) {
return `/api/v1/tenants/by-id/${this.input.tenantId}/instance`
}
return `/api/v1/tenants/by-name/${this.input.tenant}/instance`
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid truthy checks for tenantId in routing and error filters.

At Line 54 and Line 73, using this.input.tenantId as a truthy guard can pick the wrong branch for falsy string values, producing incorrect paths/error metadata. Use discriminant presence checks consistently.

Proposed fix
   protected override getPath(): string {
-    if ("tenantId" in this.input && this.input.tenantId) {
+    if ("tenantId" in this.input) {
       return `/api/v1/tenants/by-id/${this.input.tenantId}/instance`
     }
     return `/api/v1/tenants/by-name/${this.input.tenant}/instance`
   }
@@
   protected override handleClientError(error: ClientError): void {
     if (error.status === 404) {
+      const isById = "tenantId" in this.input
       throw new NotFoundException("Tenant", {
-        [this.input.tenantId ? "id" : "name"]: this.input.tenantId ?? this.input.tenant,
+        [isById ? "id" : "name"]: isById ? this.input.tenantId : this.input.tenant,
       })
     }
     throw error
   }

Also applies to: 72-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/tenant/tenant-instance.fetch.ts` around lines 54 - 57, The
routing and error-filter branches use a truthy check on this.input.tenantId
which fails for valid falsy strings; change the guards to use a discriminant
presence check (e.g. if ("tenantId" in this.input) rather than if ("tenantId" in
this.input && this.input.tenantId")) in the tenant-instance.fetch code paths
(the branch that returns
`/api/v1/tenants/by-id/${this.input.tenantId}/instance`) and update the
corresponding error/filter logic that currently checks this.input.tenantId
truthiness to also use the `"tenantId" in this.input` presence check so empty or
falsy string IDs route and produce metadata correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/contracts/tenant.ts (1)

47-47: Align tenant.create response parsing with the newly nullable contract.

Line 47 and Line 70 now allow configurationRepoCredentials to be null, but src/commands/tenant/tenant.create.ts:21-37 still expects Type.String(). That parser can reject valid responses if create returns null.

Suggested follow-up diff (outside this file)
-        configurationRepoCredentials: Type.String(),
+        configurationRepoCredentials: Type.Union([Type.String(), Type.Null()]),

Also applies to: 70-70

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contracts/tenant.ts` at line 47, The response parser in tenant.create
(src/commands/tenant/tenant.create.ts) still expects
configurationRepoCredentials as Type.String(), but the contract
(configurationRepoCredentials) is now nullable (TUnion<[TString, TNull]>), so
update the parser to accept both string and null (e.g., use
Type.Union([Type.String(), Type.Null()]) or equivalent) and then adjust
downstream handling for when configurationRepoCredentials is null; repeat the
same change for the other parser occurrence referenced around line 70 so both
parsing sites match the nullable contract.
src/common/command.ts (1)

62-67: Consider adding tenantId support to getDedicatedBaseUrl for future commands that may accept ID-only inputs.

Lines 62–67 currently extract only input.tenant for dedicated URL resolution. While all existing dedicated-subdomain commands provide a tenant field, TenantInstanceFetchCommand supports both by-name and by-id lookup (evident from its getPath() method). If a future command accepts only tenantId without a tenant field, the dedicated routing would be silently skipped. To maintain consistency with the command's broader capabilities, consider branching to the by-id fetch when only tenantId is available:

const inputTenant = /* extract tenant name */
const inputTenantId = /* extract tenantId if tenant missing */

if (!inputTenant && !inputTenantId) {
  return null
}

let tenant = tenantCache.get(inputTenant ?? inputTenantId)
if (!tenant) {
  const { TenantInstanceFetchCommand } = await import(...)
  tenant = await client.execute(
    new TenantInstanceFetchCommand(
      inputTenant ? { tenant: inputTenant } : { tenantId: inputTenantId }
    )
  )
  tenantCache.set(inputTenant ?? inputTenantId, tenant)
}

Also applies to: 72–76

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/common/command.ts` around lines 62 - 67, getDedicatedBaseUrl currently
only checks this.input.tenant and returns null if absent, which skips dedicated
routing for commands that supply only a tenantId; update the extraction to also
check for a string tenantId from this.input (e.g. const inputTenantId = typeof
this.input === "object" && "tenantId" in this.input && typeof
this.input.tenantId === "string" && this.input.tenantId), treat the presence of
either inputTenant or inputTenantId as valid, and pass the correct shape into
TenantInstanceFetchCommand when resolving the tenant via client.execute (use
tenantCache.get(inputTenant ?? inputTenantId) and cache with the same key);
ensure the code paths and cache keys consistently use inputTenant ??
inputTenantId so both name- and id-only commands work with getDedicatedBaseUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/common/command.ts`:
- Around line 62-67: getDedicatedBaseUrl currently only checks this.input.tenant
and returns null if absent, which skips dedicated routing for commands that
supply only a tenantId; update the extraction to also check for a string
tenantId from this.input (e.g. const inputTenantId = typeof this.input ===
"object" && "tenantId" in this.input && typeof this.input.tenantId === "string"
&& this.input.tenantId), treat the presence of either inputTenant or
inputTenantId as valid, and pass the correct shape into
TenantInstanceFetchCommand when resolving the tenant via client.execute (use
tenantCache.get(inputTenant ?? inputTenantId) and cache with the same key);
ensure the code paths and cache keys consistently use inputTenant ??
inputTenantId so both name- and id-only commands work with getDedicatedBaseUrl.

In `@src/contracts/tenant.ts`:
- Line 47: The response parser in tenant.create
(src/commands/tenant/tenant.create.ts) still expects
configurationRepoCredentials as Type.String(), but the contract
(configurationRepoCredentials) is now nullable (TUnion<[TString, TNull]>), so
update the parser to accept both string and null (e.g., use
Type.Union([Type.String(), Type.Null()]) or equivalent) and then adjust
downstream handling for when configurationRepoCredentials is null; repeat the
same change for the other parser occurrence referenced around line 70 so both
parsing sites match the nullable contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 736ecda2-c1e9-40d8-9918-4a607ff33e34

📥 Commits

Reviewing files that changed from the base of the PR and between f87338c and 7ba55c2.

📒 Files selected for processing (2)
  • src/common/command.ts
  • src/contracts/tenant.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jbiskur jbiskur merged commit c9cb49b into main Mar 4, 2026
2 checks passed
@jbiskur jbiskur deleted the feature/tenant-instance-fetch-command branch March 4, 2026 14:11
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.

1 participant