Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/commands/crashlytics-heapdumps-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const command = new Command("crashlytics:heapdumps:status")
table.push([
appId,
config.heapDumpCollectionEnabled ? clc.green("Enabled") : clc.red("Disabled"),
config.gcsBucket || clc.yellow("(none)"),
config.bucket || clc.yellow("(none)"),
]);

logger.info("");
Expand Down
12 changes: 6 additions & 6 deletions src/commands/crashlytics-heapdumps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe("crashlytics:heapdumps commands", () => {
describe("crashlytics:heapdumps:status", () => {
it("should return the status and configuration when enabled", async () => {
sinon.stub(profilingManager, "getProfilingManagerConfig").resolves({
gcsBucket: bucketName,
bucket: bucketName,
heapDumpCollectionEnabled: true,
});

Expand All @@ -189,14 +189,14 @@ describe("crashlytics:heapdumps commands", () => {

expect(result).to.deep.equal({
appId,
gcsBucket: bucketName,
bucket: bucketName,
heapDumpCollectionEnabled: true,
});
});

it("should return the status and configuration when disabled with no bucket", async () => {
sinon.stub(profilingManager, "getProfilingManagerConfig").resolves({
gcsBucket: "",
bucket: "",
heapDumpCollectionEnabled: false,
});

Expand All @@ -207,14 +207,14 @@ describe("crashlytics:heapdumps commands", () => {

expect(result).to.deep.equal({
appId,
gcsBucket: "",
bucket: "",
heapDumpCollectionEnabled: false,
});
});

it("should register and run crashlytics.heapdumps commands via command loader", async () => {
sinon.stub(profilingManager, "getProfilingManagerConfig").resolves({
gcsBucket: bucketName,
bucket: bucketName,
heapDumpCollectionEnabled: true,
});

Expand All @@ -239,7 +239,7 @@ describe("crashlytics:heapdumps commands", () => {

expect(result).to.deep.equal({
appId,
gcsBucket: bucketName,
bucket: bucketName,
heapDumpCollectionEnabled: true,
});
});
Expand Down
139 changes: 69 additions & 70 deletions src/crashlytics/profilingManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ describe("profilingManager", () => {
const hashedAppId = "abcdef123456";
const appId = `1:${projectNumber}:android:${hashedAppId}`;
const expectedBucketName = `firebasecrashlytics-heap-dumps-${hashedAppId}`;
const expectedBucketResourceName = `projects/_/buckets/${expectedBucketName}`;
const projectId = "my-test-project";
const resourcePath = `/v1alpha/projects/${projectNumber}/apps/${appId}/profilingManagerConfig`;

afterEach(() => {
nock.cleanAll();
Expand Down Expand Up @@ -175,34 +177,23 @@ describe("profilingManager", () => {
describe("getProfilingManagerConfig", () => {
it("should return the configuration on successful response", async () => {
const mockResponse = {
configuration: {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
},
name: `projects/${projectNumber}/apps/${appId}/profilingManagerConfig`,
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
};

nock(crashlyticsApiOrigin())
.get(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`)
.reply(200, mockResponse);
nock(crashlyticsApiOrigin()).get(resourcePath).reply(200, mockResponse);

const result = await getProfilingManagerConfig(appId);
expect(result).to.deep.equal({
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
});
expect(result).to.deep.equal(mockResponse);
expect(nock.isDone()).to.be.true;
});

it("should return default values when configuration is empty", async () => {
nock(crashlyticsApiOrigin())
.get(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`)
.reply(200, {});
it("should return empty object when configuration is empty", async () => {
nock(crashlyticsApiOrigin()).get(resourcePath).reply(200, {});

const result = await getProfilingManagerConfig(appId);
expect(result).to.deep.equal({
gcsBucket: "",
heapDumpCollectionEnabled: false,
});
expect(result).to.deep.equal({});
expect(nock.isDone()).to.be.true;
});

Expand All @@ -215,49 +206,64 @@ describe("profilingManager", () => {
});

describe("updateProfilingManagerConfig", () => {
it("should send the update request successfully", async () => {
it("should send the PATCH update request with field mask successfully", async () => {
nock(crashlyticsApiOrigin())
.post(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`, {
projectNumber,
gmpAppId: appId,
configuration: {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
},
.patch(resourcePath, {
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
})
.reply(200, {});
.query({ updateMask: "bucket,heapDumpCollectionEnabled" })
.reply(200, {
name: `projects/${projectNumber}/apps/${appId}/profilingManagerConfig`,
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
});

await updateProfilingManagerConfig(appId, {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
});
await updateProfilingManagerConfig(
appId,
{
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
},
"bucket,heapDumpCollectionEnabled",
);

expect(nock.isDone()).to.be.true;
});

it("should default empty configuration fields when omitted", async () => {
it("should send partial updateMask when updating only heapDumpCollectionEnabled", async () => {
nock(crashlyticsApiOrigin())
.post(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`, {
projectNumber,
gmpAppId: appId,
configuration: {
gcsBucket: "",
heapDumpCollectionEnabled: false,
},
.patch(resourcePath, {
heapDumpCollectionEnabled: false,
})
.reply(200, {});
.query({ updateMask: "heapDumpCollectionEnabled" })
.reply(200, {
name: `projects/${projectNumber}/apps/${appId}/profilingManagerConfig`,
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: false,
});

await updateProfilingManagerConfig(appId, {});
await updateProfilingManagerConfig(
appId,
{
heapDumpCollectionEnabled: false,
},
"heapDumpCollectionEnabled",
);

expect(nock.isDone()).to.be.true;
});

it("should throw a FirebaseError if the appId is invalid", async () => {
await expect(
updateProfilingManagerConfig("invalid-app", {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
}),
updateProfilingManagerConfig(
"invalid-app",
{
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
},
"bucket,heapDumpCollectionEnabled",
),
).to.be.rejectedWith(FirebaseError, "Unable to get the projectId from the AppId.");
});
});
Expand Down Expand Up @@ -453,41 +459,34 @@ describe("profilingManager", () => {
sinon.stub(serviceusage, "generateServiceIdentityAndPoll").resolves();
sinon.stub(resourceManager, "serviceAccountHasRoles").resolves(true);
nock(crashlyticsApiOrigin())
.post(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`, {
projectNumber,
gmpAppId: appId,
configuration: {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
},
.patch(resourcePath, {
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
})
.reply(200, {});
.query({ updateMask: "bucket,heapDumpCollectionEnabled" })
.reply(200, {
name: `projects/${projectNumber}/apps/${appId}/profilingManagerConfig`,
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: true,
});

const result = await enableHeapDumpCollection(projectId, appId);

expect(result).to.equal(expectedBucketName);
expect(nock.isDone()).to.be.true;
});

it("should preserve existing bucket name and disable heap dump collection", async () => {
it("should disable heap dump collection via partial updateMask while preserving bucket", async () => {
nock(crashlyticsApiOrigin())
.get(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`)
.patch(resourcePath, {
heapDumpCollectionEnabled: false,
})
.query({ updateMask: "heapDumpCollectionEnabled" })
.reply(200, {
configuration: {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: true,
},
name: `projects/${projectNumber}/apps/${appId}/profilingManagerConfig`,
bucket: expectedBucketResourceName,
heapDumpCollectionEnabled: false,
});
nock(crashlyticsApiOrigin())
.post(`/v1/projects/${projectNumber}/apps/${appId}/appconfig:profilingManager`, {
projectNumber,
gmpAppId: appId,
configuration: {
gcsBucket: expectedBucketName,
heapDumpCollectionEnabled: false,
},
})
.reply(200, {});

await disableHeapDumpCollection(appId);

Expand Down
Loading
Loading