Skip to content

Commit bcc5a08

Browse files
joehanbkendall
andauthored
Jules unit tests for apphosting MCP tools (#9030)
Co-authored-by: Bryan Kendall <[email protected]>
1 parent 0e89756 commit bcc5a08

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { expect } from "chai";
2+
import * as sinon from "sinon";
3+
import { fetch_logs } from "./fetch_logs";
4+
import * as apphosting from "../../../gcp/apphosting";
5+
import * as run from "../../../gcp/run";
6+
import * as cloudlogging from "../../../gcp/cloudlogging";
7+
import { FirebaseError } from "../../../error";
8+
import { toContent } from "../../util";
9+
10+
describe("fetch_logs tool", () => {
11+
const projectId = "test-project";
12+
const location = "us-central1";
13+
const backendId = "test-backend";
14+
15+
let getBackendStub: sinon.SinonStub;
16+
let getTrafficStub: sinon.SinonStub;
17+
let listBuildsStub: sinon.SinonStub;
18+
let fetchServiceLogsStub: sinon.SinonStub;
19+
let listEntriesStub: sinon.SinonStub;
20+
21+
beforeEach(() => {
22+
getBackendStub = sinon.stub(apphosting, "getBackend");
23+
getTrafficStub = sinon.stub(apphosting, "getTraffic");
24+
listBuildsStub = sinon.stub(apphosting, "listBuilds");
25+
fetchServiceLogsStub = sinon.stub(run, "fetchServiceLogs");
26+
listEntriesStub = sinon.stub(cloudlogging, "listEntries");
27+
});
28+
29+
afterEach(() => {
30+
sinon.restore();
31+
});
32+
33+
it("should return message if backendId is not specified", async () => {
34+
const result = await fetch_logs.fn({}, { projectId } as any);
35+
expect(result).to.deep.equal(toContent("backendId must be specified."));
36+
});
37+
38+
context("when buildLogs is false", () => {
39+
it("should fetch service logs successfully", async () => {
40+
const backend = {
41+
name: `projects/${projectId}/locations/${location}/backends/${backendId}`,
42+
managedResources: [
43+
{
44+
runService: {
45+
service: `projects/${projectId}/locations/${location}/services/service-id`,
46+
},
47+
},
48+
],
49+
};
50+
const traffic = {
51+
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
52+
};
53+
const logs = ["log entry 1", "log entry 2"];
54+
55+
getBackendStub.resolves(backend);
56+
getTrafficStub.resolves(traffic);
57+
fetchServiceLogsStub.resolves(logs);
58+
59+
const result = await fetch_logs.fn({ backendId, location }, { projectId } as any);
60+
61+
expect(getBackendStub).to.be.calledWith(projectId, location, backendId);
62+
expect(getTrafficStub).to.be.calledWith(projectId, location, backendId);
63+
expect(fetchServiceLogsStub).to.be.calledWith(projectId, "service-id");
64+
expect(result).to.deep.equal(toContent(logs));
65+
});
66+
67+
it("should throw FirebaseError if service name cannot be determined", async () => {
68+
const backend = {
69+
name: `projects/${projectId}/locations/${location}/backends/${backendId}`,
70+
managedResources: [],
71+
};
72+
const traffic = {
73+
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
74+
};
75+
76+
getBackendStub.resolves(backend);
77+
getTrafficStub.resolves(traffic);
78+
79+
await expect(fetch_logs.fn({ backendId, location }, { projectId } as any)).to.be.rejectedWith(
80+
FirebaseError,
81+
"Unable to get service name from managedResources.",
82+
);
83+
});
84+
});
85+
86+
context("when buildLogs is true", () => {
87+
const buildLogsUri = `https://console.cloud.google.com/build/region=${location}/12345`;
88+
const build = { createTime: new Date().toISOString(), buildLogsUri };
89+
const builds = { builds: [build] };
90+
91+
it("should fetch build logs successfully", async () => {
92+
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` };
93+
const traffic = {
94+
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
95+
};
96+
const logEntries = [{ textPayload: "build log 1" }];
97+
98+
getBackendStub.resolves(backend);
99+
getTrafficStub.resolves(traffic);
100+
listBuildsStub.resolves(builds);
101+
listEntriesStub.resolves(logEntries);
102+
103+
const result = await fetch_logs.fn({ buildLogs: true, backendId, location }, {
104+
projectId,
105+
} as any);
106+
107+
expect(listBuildsStub).to.be.calledWith(projectId, location, backendId);
108+
expect(listEntriesStub).to.be.calledOnce;
109+
expect(listEntriesStub.args[0][1]).to.include('resource.labels.build_id="12345"');
110+
expect(result).to.deep.equal(toContent(logEntries));
111+
});
112+
113+
it("should return 'No logs found.' if no build logs are available", async () => {
114+
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` };
115+
const traffic = {
116+
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
117+
};
118+
119+
getBackendStub.resolves(backend);
120+
getTrafficStub.resolves(traffic);
121+
listBuildsStub.resolves(builds);
122+
listEntriesStub.resolves([]);
123+
124+
const result = await fetch_logs.fn({ buildLogs: true, backendId, location }, {
125+
projectId,
126+
} as any);
127+
expect(result).to.deep.equal(toContent("No logs found."));
128+
});
129+
130+
it("should throw FirebaseError if build ID cannot be determined from buildLogsUri", async () => {
131+
const buildWithInvalidUri = {
132+
createTime: new Date().toISOString(),
133+
buildLogsUri: "invalid-uri",
134+
};
135+
const buildsWithInvalidUri = { builds: [buildWithInvalidUri] };
136+
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` };
137+
const traffic = {
138+
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
139+
};
140+
141+
getBackendStub.resolves(backend);
142+
getTrafficStub.resolves(traffic);
143+
listBuildsStub.resolves(buildsWithInvalidUri);
144+
145+
await expect(
146+
fetch_logs.fn({ buildLogs: true, backendId, location }, { projectId } as any),
147+
).to.be.rejectedWith(FirebaseError, "Unable to determine the build ID.");
148+
});
149+
});
150+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { expect } from "chai";
2+
import * as sinon from "sinon";
3+
import { list_backends } from "./list_backends";
4+
import * as apphosting from "../../../gcp/apphosting";
5+
import { toContent } from "../../util";
6+
7+
describe("list_backends tool", () => {
8+
const projectId = "test-project";
9+
const location = "us-central1";
10+
const backendId = "test-backend";
11+
12+
let listBackendsStub: sinon.SinonStub;
13+
let getTrafficStub: sinon.SinonStub;
14+
let listDomainsStub: sinon.SinonStub;
15+
let parseBackendNameStub: sinon.SinonStub;
16+
17+
beforeEach(() => {
18+
listBackendsStub = sinon.stub(apphosting, "listBackends");
19+
getTrafficStub = sinon.stub(apphosting, "getTraffic");
20+
listDomainsStub = sinon.stub(apphosting, "listDomains");
21+
parseBackendNameStub = sinon.stub(apphosting, "parseBackendName");
22+
});
23+
24+
afterEach(() => {
25+
sinon.restore();
26+
});
27+
28+
it("should return a message when no backends are found", async () => {
29+
listBackendsStub.resolves({ backends: [] });
30+
31+
const result = await list_backends.fn({ location }, { projectId } as any);
32+
33+
expect(listBackendsStub).to.be.calledWith(projectId, location);
34+
expect(result).to.deep.equal(
35+
toContent(`No backends exist for project ${projectId} in ${location}.`),
36+
);
37+
});
38+
39+
it("should list backends with traffic and domain info", async () => {
40+
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` };
41+
const backends = { backends: [backend] };
42+
const traffic = { name: "traffic" };
43+
const domains = [{ name: "domain" }];
44+
45+
listBackendsStub.resolves(backends);
46+
parseBackendNameStub.returns({ location, id: backendId });
47+
getTrafficStub.resolves(traffic);
48+
listDomainsStub.resolves(domains);
49+
50+
const result = await list_backends.fn({ location }, { projectId } as any);
51+
52+
expect(listBackendsStub).to.be.calledWith(projectId, location);
53+
expect(parseBackendNameStub).to.be.calledWith(backend.name);
54+
expect(getTrafficStub).to.be.calledWith(projectId, location, backendId);
55+
expect(listDomainsStub).to.be.calledWith(projectId, location, backendId);
56+
57+
const expectedData = [{ ...backend, traffic, domains }];
58+
expect(result).to.deep.equal(toContent(expectedData));
59+
});
60+
61+
it("should handle the default location", async () => {
62+
listBackendsStub.resolves({ backends: [] });
63+
await list_backends.fn({}, { projectId } as any);
64+
expect(listBackendsStub).to.be.calledWith(projectId, "-");
65+
});
66+
});

0 commit comments

Comments
 (0)