Skip to content

Commit 20bd3d3

Browse files
committed
feat(deploy/mock): inject production_instance_exists and unsupported plan features
1 parent 387f281 commit 20bd3d3

2 files changed

Lines changed: 80 additions & 3 deletions

File tree

packages/cli-core/src/commands/deploy/mock.test.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
resolveTestDeployFlags,
66
withMockProductionInstance,
77
withTestFailureAfterApiCall,
8+
mockDeployApi,
9+
configureMockDeployApi,
10+
_resetDeployStatusMock,
811
} from "./mock.ts";
912

1013
describe("resolveTestDeployFlags", () => {
@@ -14,7 +17,9 @@ describe("resolveTestDeployFlags", () => {
1417
testFailProductionInstanceCheck: false,
1518
testFailDomainLookup: false,
1619
testFailValidateCloning: false,
20+
testFailValidateCloningUnsupportedFeatures: undefined,
1721
testFailCreateProductionInstance: false,
22+
testFailCreateProductionInstanceExists: false,
1823
testFailDnsVerification: false,
1924
testFailOAuthSave: false,
2025
});
@@ -31,7 +36,9 @@ describe("resolveTestDeployFlags", () => {
3136
testFailProductionInstanceCheck: false,
3237
testFailDomainLookup: false,
3338
testFailValidateCloning: false,
39+
testFailValidateCloningUnsupportedFeatures: undefined,
3440
testFailCreateProductionInstance: false,
41+
testFailCreateProductionInstanceExists: false,
3542
testFailDnsVerification: true,
3643
testFailOAuthSave: false,
3744
});
@@ -156,8 +163,41 @@ describe("withMockProductionInstance", () => {
156163

157164
const result = withMockProductionInstance(stagingOnly);
158165
expect(result.instances).toHaveLength(2);
159-
expect(
160-
result.instances.some((instance) => instance.environment_type === "production"),
161-
).toBe(true);
166+
expect(result.instances.some((instance) => instance.environment_type === "production")).toBe(
167+
true,
168+
);
169+
});
170+
});
171+
172+
describe("mockDeployApi failure injection — specific error codes", () => {
173+
test("failCreateProductionInstanceExists throws PlapiError with code production_instance_exists", async () => {
174+
_resetDeployStatusMock();
175+
configureMockDeployApi({ failCreateProductionInstanceExists: true });
176+
let thrown: unknown;
177+
try {
178+
await mockDeployApi.createProductionInstance("app_x", { home_url: "https://example.com" });
179+
} catch (e) {
180+
thrown = e;
181+
}
182+
expect(thrown).toBeInstanceOf(PlapiError);
183+
const err = thrown as PlapiError;
184+
expect(err.status).toBe(400);
185+
expect(err.code).toBe("production_instance_exists");
186+
});
187+
188+
test("failValidateCloningUnsupportedFeatures throws 402 with meta.unsupported_features", async () => {
189+
_resetDeployStatusMock();
190+
configureMockDeployApi({ failValidateCloningUnsupportedFeatures: ["saml", "mfa"] });
191+
let thrown: unknown;
192+
try {
193+
await mockDeployApi.validateCloning("app_x", { clone_instance_id: "ins_dev" });
194+
} catch (e) {
195+
thrown = e;
196+
}
197+
expect(thrown).toBeInstanceOf(PlapiError);
198+
const err = thrown as PlapiError;
199+
expect(err.status).toBe(402);
200+
expect(err.code).toBe("unsupported_subscription_plan_features");
201+
expect(err.meta).toEqual({ unsupported_features: ["saml", "mfa"] });
162202
});
163203
});

packages/cli-core/src/commands/deploy/mock.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ const MOCK_INCOMPLETE_POLLS = 2;
2121

2222
export type DeployApiMockOptions = {
2323
failValidateCloning?: boolean;
24+
/** Simulates HTTP 402 unsupported_subscription_plan_features with this feature list. */
25+
failValidateCloningUnsupportedFeatures?: string[];
2426
failCreateProductionInstance?: boolean;
27+
/** Simulates HTTP 400 production_instance_exists. */
28+
failCreateProductionInstanceExists?: boolean;
2529
failDnsVerification?: boolean;
2630
failOAuthSave?: boolean;
2731
};
@@ -31,7 +35,9 @@ export type DeployTestFlags = {
3135
testFailProductionInstanceCheck?: boolean;
3236
testFailDomainLookup?: boolean;
3337
testFailValidateCloning?: boolean;
38+
testFailValidateCloningUnsupportedFeatures?: string[];
3439
testFailCreateProductionInstance?: boolean;
40+
testFailCreateProductionInstanceExists?: boolean;
3541
testFailDnsVerification?: boolean;
3642
testFailOAuthSave?: boolean;
3743
};
@@ -47,7 +53,9 @@ export function resolveTestDeployFlags(options: {
4753
testFailProductionInstanceCheck?: boolean;
4854
testFailDomainLookup?: boolean;
4955
testFailValidateCloning?: boolean;
56+
testFailValidateCloningUnsupportedFeatures?: string[];
5057
testFailCreateProductionInstance?: boolean;
58+
testFailCreateProductionInstanceExists?: boolean;
5159
testFailDnsVerification?: boolean;
5260
testFailOAuthSave?: boolean;
5361
}): DeployTestFlags {
@@ -56,7 +64,9 @@ export function resolveTestDeployFlags(options: {
5664
testFailProductionInstanceCheck: options.testFailProductionInstanceCheck === true,
5765
testFailDomainLookup: options.testFailDomainLookup === true,
5866
testFailValidateCloning: options.testFailValidateCloning === true,
67+
testFailValidateCloningUnsupportedFeatures: options.testFailValidateCloningUnsupportedFeatures,
5968
testFailCreateProductionInstance: options.testFailCreateProductionInstance === true,
69+
testFailCreateProductionInstanceExists: options.testFailCreateProductionInstanceExists === true,
6070
testFailDnsVerification: options.testFailDnsVerification === true,
6171
testFailOAuthSave: options.testFailOAuthSave === true,
6272
};
@@ -105,12 +115,31 @@ export function _resetDeployStatusMock(): void {
105115
configureMockDeployApi();
106116
}
107117

118+
function simulatedSpecificFailure(
119+
status: number,
120+
code: string,
121+
message: string,
122+
meta?: Record<string, unknown>,
123+
): PlapiError {
124+
const body = JSON.stringify({
125+
errors: [{ code, message, ...(meta ? { meta } : {}) }],
126+
});
127+
return PlapiError.fromBody(status, body, "clerk deploy mock");
128+
}
129+
108130
export const mockDeployApi: DeployApi = {
109131
async createProductionInstance(_applicationId, params) {
110132
await simulateServerLatency();
111133
if (mockOptions.failCreateProductionInstance) {
112134
throw simulatedDeployApiFailure("production instance creation");
113135
}
136+
if (mockOptions.failCreateProductionInstanceExists) {
137+
throw simulatedSpecificFailure(
138+
400,
139+
"production_instance_exists",
140+
"You can only have one production instance.",
141+
);
142+
}
114143
return {
115144
instance_id: MOCK_PRODUCTION_INSTANCE_ID,
116145
environment_type: "production",
@@ -129,6 +158,14 @@ export const mockDeployApi: DeployApi = {
129158
if (mockOptions.failValidateCloning) {
130159
throw simulatedDeployApiFailure("cloning validation");
131160
}
161+
if (mockOptions.failValidateCloningUnsupportedFeatures) {
162+
throw simulatedSpecificFailure(
163+
402,
164+
"unsupported_subscription_plan_features",
165+
"Unsupported plan features",
166+
{ unsupported_features: mockOptions.failValidateCloningUnsupportedFeatures },
167+
);
168+
}
132169
},
133170

134171
async getDeployStatus(applicationId, envOrInsId) {

0 commit comments

Comments
 (0)