Skip to content

Commit f1f95e8

Browse files
authored
fix: use app display name instead of project display name in AI Logic provisioning (#9224)
1 parent e6bf8e4 commit f1f95e8

File tree

8 files changed

+77
-39
lines changed

8 files changed

+77
-39
lines changed

src/init/features/ailogic/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ describe("init ailogic", () => {
5151
expect(mockSetup.featureInfo).to.have.property("ailogic");
5252
expect(mockSetup.featureInfo?.ailogic).to.deep.equal({
5353
appId: "1:123456789:android:abcdef123456",
54+
displayName: "Test Android App",
5455
});
5556
});
5657

src/init/features/ailogic/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111

1212
export interface AiLogicInfo {
1313
appId: string;
14+
displayName?: string;
1415
}
1516

1617
function checkForApps(apps: AppMetadata[]): void {
@@ -69,25 +70,29 @@ export async function askQuestions(setup: Setup): Promise<void> {
6970

7071
setup.featureInfo.ailogic = {
7172
appId: selectedApp.appId,
73+
displayName: selectedApp.displayName,
7274
};
7375
}
7476

75-
function getAppOptions(appInfo: AppInfo): ProvisionAppOptions {
77+
function getAppOptions(appInfo: AppInfo, displayName?: string): ProvisionAppOptions {
7678
switch (appInfo.platform) {
7779
case AppPlatform.IOS:
7880
return {
7981
platform: AppPlatform.IOS,
8082
appId: appInfo.appId,
83+
displayName,
8184
};
8285
case AppPlatform.ANDROID:
8386
return {
8487
platform: AppPlatform.ANDROID,
8588
appId: appInfo.appId,
89+
displayName,
8690
};
8791
case AppPlatform.WEB:
8892
return {
8993
platform: AppPlatform.WEB,
9094
appId: appInfo.appId,
95+
displayName,
9196
};
9297
default:
9398
throw new FirebaseError(`Unsupported platform ${appInfo.platform}`, { exit: 1 });
@@ -115,10 +120,9 @@ export async function actuate(setup: Setup): Promise<void> {
115120
// Build provision options and call API directly
116121
const provisionOptions: ProvisionFirebaseAppOptions = {
117122
project: {
118-
displayName: "Firebase Project",
119123
parent: { type: "existing_project", projectId: setup.projectId },
120124
},
121-
app: getAppOptions(appInfo),
125+
app: getAppOptions(appInfo, ailogicInfo.displayName),
122126
features: {
123127
firebaseAiLogicInput: {},
124128
},

src/init/features/ailogic/utils.spec.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ describe("ailogic utils", () => {
139139
});
140140

141141
describe("validateAppExists", () => {
142-
let getAppConfigStub: sinon.SinonStub;
142+
let listFirebaseAppsStub: sinon.SinonStub;
143143

144144
beforeEach(() => {
145-
getAppConfigStub = sandbox.stub(apps, "getAppConfig");
145+
listFirebaseAppsStub = sandbox.stub(apps, "listFirebaseApps");
146146
});
147147

148148
it("should not throw when app exists for web platform", async () => {
@@ -151,10 +151,14 @@ describe("ailogic utils", () => {
151151
appId: "1:123456789:web:abcdef",
152152
platform: AppPlatform.WEB,
153153
};
154-
getAppConfigStub.resolves({ mockConfig: true });
154+
const mockApps = [
155+
{ appId: "1:123456789:web:abcdef", displayName: "Test App", platform: AppPlatform.WEB },
156+
];
157+
listFirebaseAppsStub.resolves(mockApps);
155158

156-
await expect(utils.validateAppExists(appInfo)).to.not.be.rejected;
157-
sinon.assert.calledWith(getAppConfigStub, "1:123456789:web:abcdef", AppPlatform.WEB);
159+
const result = await utils.validateAppExists(appInfo, "test-project");
160+
expect(result).to.deep.equal(mockApps[0]);
161+
sinon.assert.calledWith(listFirebaseAppsStub, "test-project", AppPlatform.WEB);
158162
});
159163

160164
it("should not throw when app exists for ios platform", async () => {
@@ -163,10 +167,14 @@ describe("ailogic utils", () => {
163167
appId: "1:123456789:ios:abcdef",
164168
platform: AppPlatform.IOS,
165169
};
166-
getAppConfigStub.resolves({ mockConfig: true });
170+
const mockApps = [
171+
{ appId: "1:123456789:ios:abcdef", displayName: "Test iOS App", platform: AppPlatform.IOS },
172+
];
173+
listFirebaseAppsStub.resolves(mockApps);
167174

168-
await expect(utils.validateAppExists(appInfo)).to.not.be.rejected;
169-
sinon.assert.calledWith(getAppConfigStub, "1:123456789:ios:abcdef", AppPlatform.IOS);
175+
const result = await utils.validateAppExists(appInfo, "test-project");
176+
expect(result).to.deep.equal(mockApps[0]);
177+
sinon.assert.calledWith(listFirebaseAppsStub, "test-project", AppPlatform.IOS);
170178
});
171179

172180
it("should not throw when app exists for android platform", async () => {
@@ -175,10 +183,18 @@ describe("ailogic utils", () => {
175183
appId: "1:123456789:android:abcdef",
176184
platform: AppPlatform.ANDROID,
177185
};
178-
getAppConfigStub.resolves({ mockConfig: true });
186+
const mockApps = [
187+
{
188+
appId: "1:123456789:android:abcdef",
189+
displayName: "Test Android App",
190+
platform: AppPlatform.ANDROID,
191+
},
192+
];
193+
listFirebaseAppsStub.resolves(mockApps);
179194

180-
await expect(utils.validateAppExists(appInfo)).to.not.be.rejected;
181-
sinon.assert.calledWith(getAppConfigStub, "1:123456789:android:abcdef", AppPlatform.ANDROID);
195+
const result = await utils.validateAppExists(appInfo, "test-project");
196+
expect(result).to.deep.equal(mockApps[0]);
197+
sinon.assert.calledWith(listFirebaseAppsStub, "test-project", AppPlatform.ANDROID);
182198
});
183199

184200
it("should throw when app does not exist", async () => {
@@ -187,11 +203,11 @@ describe("ailogic utils", () => {
187203
appId: "1:123456789:web:nonexistent",
188204
platform: AppPlatform.WEB,
189205
};
190-
getAppConfigStub.throws(new Error("App not found"));
206+
listFirebaseAppsStub.resolves([]);
191207

192-
await expect(utils.validateAppExists(appInfo)).to.be.rejectedWith(
208+
await expect(utils.validateAppExists(appInfo, "test-project")).to.be.rejectedWith(
193209
FirebaseError,
194-
"App 1:123456789:web:nonexistent does not exist or is not accessible.",
210+
"App 1:123456789:web:nonexistent does not exist in project test-project.",
195211
);
196212
});
197213
});

src/init/features/ailogic/utils.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AppPlatform, getAppConfig } from "../../../management/apps";
1+
import { AppPlatform, listFirebaseApps, AppMetadata } from "../../../management/apps";
22
import { FirebaseError } from "../../../error";
33
import { FirebaseProjectMetadata } from "../../../types/project";
44

@@ -81,10 +81,23 @@ export function validateProjectNumberMatch(
8181
/**
8282
* Validate that app exists
8383
*/
84-
export async function validateAppExists(appInfo: AppInfo): Promise<void> {
84+
export async function validateAppExists(appInfo: AppInfo, projectId: string): Promise<AppMetadata> {
8585
try {
86-
await getAppConfig(appInfo.appId, appInfo.platform);
86+
// Get apps list to find the specific app with metadata
87+
const apps = await listFirebaseApps(projectId, appInfo.platform);
88+
const app = apps.find((a) => a.appId === appInfo.appId);
89+
90+
if (!app) {
91+
throw new FirebaseError(`App ${appInfo.appId} does not exist in project ${projectId}.`, {
92+
exit: 1,
93+
});
94+
}
95+
96+
return app;
8797
} catch (error) {
98+
if (error instanceof FirebaseError) {
99+
throw error;
100+
}
88101
throw new FirebaseError(`App ${appInfo.appId} does not exist or is not accessible.`, {
89102
exit: 1,
90103
original: error instanceof Error ? error : new Error(String(error)),

src/management/provisioning/provision.spec.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ describe("Provision module", () => {
215215
it("should build basic request with minimal options", () => {
216216
const options: ProvisionFirebaseAppOptions = {
217217
project: { displayName: PROJECT_DISPLAY_NAME },
218-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
218+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
219219
};
220220

221221
const result = buildProvisionRequest(options);
@@ -233,7 +233,7 @@ describe("Provision module", () => {
233233
displayName: PROJECT_DISPLAY_NAME,
234234
parent: { type: "existing_project", projectId: "my-project-123" },
235235
},
236-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
236+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
237237
};
238238

239239
const result = buildProvisionRequest(options);
@@ -249,7 +249,7 @@ describe("Provision module", () => {
249249
it("should include location when specified", () => {
250250
const options: ProvisionFirebaseAppOptions = {
251251
project: { displayName: PROJECT_DISPLAY_NAME },
252-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
252+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
253253
features: { location: LOCATION },
254254
};
255255

@@ -266,7 +266,7 @@ describe("Provision module", () => {
266266
it("should include requestId when specified", () => {
267267
const options: ProvisionFirebaseAppOptions = {
268268
project: { displayName: PROJECT_DISPLAY_NAME },
269-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
269+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
270270
requestId: REQUEST_ID,
271271
};
272272

@@ -288,6 +288,7 @@ describe("Provision module", () => {
288288
bundleId: BUNDLE_ID,
289289
appStoreId: "12345",
290290
teamId: "TEAM123",
291+
displayName: PROJECT_DISPLAY_NAME,
291292
},
292293
};
293294

@@ -311,6 +312,7 @@ describe("Provision module", () => {
311312
packageName: PACKAGE_NAME,
312313
sha1Hashes: ["sha1hash1", "sha1hash2"],
313314
sha256Hashes: ["sha256hash1"],
315+
displayName: PROJECT_DISPLAY_NAME,
314316
},
315317
};
316318

@@ -329,7 +331,7 @@ describe("Provision module", () => {
329331
it("should build Web-specific request correctly", () => {
330332
const options: ProvisionFirebaseAppOptions = {
331333
project: { displayName: PROJECT_DISPLAY_NAME },
332-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
334+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
333335
};
334336

335337
const result = buildProvisionRequest(options);
@@ -345,7 +347,7 @@ describe("Provision module", () => {
345347
const aiFeatures = { enableAiLogic: true, model: "gemini-pro" };
346348
const options: ProvisionFirebaseAppOptions = {
347349
project: { displayName: PROJECT_DISPLAY_NAME },
348-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
350+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
349351
features: { firebaseAiLogicInput: aiFeatures },
350352
};
351353

@@ -411,7 +413,7 @@ describe("Provision module", () => {
411413
it("should provision Web app successfully", async () => {
412414
const options: ProvisionFirebaseAppOptions = {
413415
project: { displayName: PROJECT_DISPLAY_NAME },
414-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
416+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
415417
};
416418

417419
// Mock API call
@@ -434,7 +436,7 @@ describe("Provision module", () => {
434436
displayName: PROJECT_DISPLAY_NAME,
435437
parent: { type: "existing_project", projectId: "parent-project-123" },
436438
},
437-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
439+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
438440
};
439441

440442
// Mock API call with parent verification
@@ -459,7 +461,7 @@ describe("Provision module", () => {
459461
displayName: PROJECT_DISPLAY_NAME,
460462
parent: { type: "organization", organizationId: "987654321" },
461463
},
462-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
464+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
463465
};
464466

465467
// Mock API call with parent verification
@@ -484,7 +486,7 @@ describe("Provision module", () => {
484486
displayName: PROJECT_DISPLAY_NAME,
485487
parent: { type: "folder", folderId: "123456789" },
486488
},
487-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
489+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
488490
};
489491

490492
// Mock API call with parent verification
@@ -506,7 +508,7 @@ describe("Provision module", () => {
506508
it("should provision with requestId for idempotency", async () => {
507509
const options: ProvisionFirebaseAppOptions = {
508510
project: { displayName: PROJECT_DISPLAY_NAME },
509-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
511+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
510512
requestId: REQUEST_ID,
511513
};
512514

@@ -529,7 +531,7 @@ describe("Provision module", () => {
529531
it("should provision with custom location", async () => {
530532
const options: ProvisionFirebaseAppOptions = {
531533
project: { displayName: PROJECT_DISPLAY_NAME },
532-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
534+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
533535
features: { location: LOCATION },
534536
};
535537

@@ -553,7 +555,7 @@ describe("Provision module", () => {
553555
const aiFeatures = { enableAiLogic: true, model: "gemini-pro" };
554556
const options: ProvisionFirebaseAppOptions = {
555557
project: { displayName: PROJECT_DISPLAY_NAME },
556-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
558+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
557559
features: { firebaseAiLogicInput: aiFeatures },
558560
};
559561

@@ -644,7 +646,7 @@ describe("Provision module", () => {
644646

645647
const baseOptions: ProvisionFirebaseAppOptions = {
646648
project: { displayName: PROJECT_DISPLAY_NAME },
647-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
649+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
648650
};
649651

650652
it("should work without requestId (undefined)", async () => {
@@ -770,7 +772,7 @@ describe("Provision module", () => {
770772
describe("provisionFirebaseApp - Error Cases", () => {
771773
const baseOptions: ProvisionFirebaseAppOptions = {
772774
project: { displayName: PROJECT_DISPLAY_NAME },
773-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
775+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
774776
};
775777

776778
it("should reject if API call fails with 404", async () => {
@@ -976,7 +978,7 @@ describe("Provision module", () => {
976978
it("should require webAppId for Web apps", async () => {
977979
const options: ProvisionFirebaseAppOptions = {
978980
project: { displayName: PROJECT_DISPLAY_NAME },
979-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
981+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
980982
};
981983

982984
// Mock API call to verify webAppId is included as appNamespace
@@ -1094,7 +1096,7 @@ describe("Provision module", () => {
10941096

10951097
const baseOptions: ProvisionFirebaseAppOptions = {
10961098
project: { displayName: PROJECT_DISPLAY_NAME },
1097-
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID },
1099+
app: { platform: AppPlatform.WEB, webAppId: WEB_APP_ID, displayName: PROJECT_DISPLAY_NAME },
10981100
};
10991101

11001102
it("should call correct API endpoint", async () => {

src/management/provisioning/provision.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export function buildProvisionRequest(
8686

8787
return {
8888
appNamespace: buildAppNamespace(options.app),
89-
displayName: options.project.displayName,
89+
displayName: options.app.displayName,
9090
...(options.project.parent && { parent: buildParentString(options.project.parent) }),
9191
...(options.features?.location && { location: options.features.location }),
9292
...(options.requestId && { requestId: options.requestId }),

src/management/provisioning/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AppPlatform } from "../apps";
33
interface BaseProvisionAppOptions {
44
platform: AppPlatform;
55
appId?: string;
6+
displayName?: string;
67
}
78

89
interface IosAppOptions extends BaseProvisionAppOptions {

src/mcp/tools/core/init.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,12 @@ export const init = tool(
208208
const appInfo = parseAppId(features.ailogic.app_id);
209209
const projectInfo = await getFirebaseProject(projectId);
210210
validateProjectNumberMatch(appInfo, projectInfo);
211-
await validateAppExists(appInfo);
211+
const appData = await validateAppExists(appInfo, projectId);
212212

213213
featuresList.push("ailogic");
214214
featureInfo.ailogic = {
215215
appId: features.ailogic.app_id,
216+
displayName: appData.displayName,
216217
};
217218
}
218219
const setup: Setup = {

0 commit comments

Comments
 (0)