-
Notifications
You must be signed in to change notification settings - Fork 1k
Jules unit tests for apphosting MCP tools #9030
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { expect } from "chai"; | ||
import * as sinon from "sinon"; | ||
import { fetch_logs } from "./fetch_logs"; | ||
import * as apphosting from "../../../gcp/apphosting"; | ||
import * as run from "../../../gcp/run"; | ||
import * as cloudlogging from "../../../gcp/cloudlogging"; | ||
import { FirebaseError } from "../../../error"; | ||
import { toContent } from "../../util"; | ||
|
||
describe("fetch_logs tool", () => { | ||
const projectId = "test-project"; | ||
const location = "us-central1"; | ||
const backendId = "test-backend"; | ||
|
||
let getBackendStub: sinon.SinonStub; | ||
let getTrafficStub: sinon.SinonStub; | ||
let listBuildsStub: sinon.SinonStub; | ||
let fetchServiceLogsStub: sinon.SinonStub; | ||
let listEntriesStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
getBackendStub = sinon.stub(apphosting, "getBackend"); | ||
getTrafficStub = sinon.stub(apphosting, "getTraffic"); | ||
listBuildsStub = sinon.stub(apphosting, "listBuilds"); | ||
fetchServiceLogsStub = sinon.stub(run, "fetchServiceLogs"); | ||
listEntriesStub = sinon.stub(cloudlogging, "listEntries"); | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
it("should return message if backendId is not specified", async () => { | ||
const result = await fetch_logs.fn({}, { projectId } as any); | ||
Check warning on line 34 in src/mcp/tools/apphosting/fetch_logs.spec.ts
|
||
expect(result).to.deep.equal(toContent("backendId must be specified.")); | ||
}); | ||
|
||
context("when buildLogs is false", () => { | ||
it("should fetch service logs successfully", async () => { | ||
const backend = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}`, | ||
managedResources: [ | ||
{ | ||
runService: { | ||
service: `projects/${projectId}/locations/${location}/services/service-id`, | ||
}, | ||
}, | ||
], | ||
}; | ||
const traffic = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`, | ||
}; | ||
const logs = ["log entry 1", "log entry 2"]; | ||
|
||
getBackendStub.resolves(backend); | ||
getTrafficStub.resolves(traffic); | ||
fetchServiceLogsStub.resolves(logs); | ||
|
||
const result = await fetch_logs.fn({ backendId, location }, { projectId } as any); | ||
Check warning on line 59 in src/mcp/tools/apphosting/fetch_logs.spec.ts
|
||
|
||
expect(getBackendStub).to.be.calledWith(projectId, location, backendId); | ||
expect(getTrafficStub).to.be.calledWith(projectId, location, backendId); | ||
expect(fetchServiceLogsStub).to.be.calledWith(projectId, "service-id"); | ||
expect(result).to.deep.equal(toContent(logs)); | ||
}); | ||
|
||
it("should throw FirebaseError if service name cannot be determined", async () => { | ||
const backend = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}`, | ||
managedResources: [], | ||
}; | ||
const traffic = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`, | ||
}; | ||
|
||
getBackendStub.resolves(backend); | ||
getTrafficStub.resolves(traffic); | ||
|
||
await expect(fetch_logs.fn({ backendId, location }, { projectId } as any)).to.be.rejectedWith( | ||
Check warning on line 79 in src/mcp/tools/apphosting/fetch_logs.spec.ts
|
||
FirebaseError, | ||
"Unable to get service name from managedResources.", | ||
); | ||
}); | ||
}); | ||
|
||
context("when buildLogs is true", () => { | ||
const buildLogsUri = `https://console.cloud.google.com/build/region=${location}/12345`; | ||
const build = { createTime: new Date().toISOString(), buildLogsUri }; | ||
const builds = { builds: [build] }; | ||
|
||
it("should fetch build logs successfully", async () => { | ||
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` }; | ||
const traffic = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`, | ||
}; | ||
const logEntries = [{ textPayload: "build log 1" }]; | ||
|
||
getBackendStub.resolves(backend); | ||
getTrafficStub.resolves(traffic); | ||
listBuildsStub.resolves(builds); | ||
listEntriesStub.resolves(logEntries); | ||
|
||
const result = await fetch_logs.fn({ buildLogs: true, backendId, location }, { | ||
projectId, | ||
} as any); | ||
|
||
expect(listBuildsStub).to.be.calledWith(projectId, location, backendId); | ||
expect(listEntriesStub).to.be.calledOnce; | ||
expect(listEntriesStub.args[0][1]).to.include('resource.labels.build_id="12345"'); | ||
expect(result).to.deep.equal(toContent(logEntries)); | ||
}); | ||
|
||
it("should return 'No logs found.' if no build logs are available", async () => { | ||
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` }; | ||
const traffic = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`, | ||
}; | ||
|
||
getBackendStub.resolves(backend); | ||
getTrafficStub.resolves(traffic); | ||
listBuildsStub.resolves(builds); | ||
listEntriesStub.resolves([]); | ||
|
||
const result = await fetch_logs.fn({ buildLogs: true, backendId, location }, { | ||
projectId, | ||
} as any); | ||
expect(result).to.deep.equal(toContent("No logs found.")); | ||
}); | ||
|
||
it("should throw FirebaseError if build ID cannot be determined from buildLogsUri", async () => { | ||
const buildWithInvalidUri = { | ||
createTime: new Date().toISOString(), | ||
buildLogsUri: "invalid-uri", | ||
}; | ||
const buildsWithInvalidUri = { builds: [buildWithInvalidUri] }; | ||
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` }; | ||
const traffic = { | ||
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`, | ||
}; | ||
|
||
getBackendStub.resolves(backend); | ||
getTrafficStub.resolves(traffic); | ||
listBuildsStub.resolves(buildsWithInvalidUri); | ||
|
||
await expect( | ||
fetch_logs.fn({ buildLogs: true, backendId, location }, { projectId } as any), | ||
).to.be.rejectedWith(FirebaseError, "Unable to determine the build ID."); | ||
}); | ||
}); | ||
Comment on lines
+86
to
+149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The setup for context("when buildLogs is true", () => {
const buildLogsUri = `https://console.cloud.google.com/build/region=${location}/12345`;
const build = { createTime: new Date().toISOString(), buildLogsUri };
const builds = { builds: [build] };
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` };
const traffic = {
name: `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`,
};
beforeEach(() => {
getBackendStub.resolves(backend);
getTrafficStub.resolves(traffic);
});
it("should fetch build logs successfully", async () => {
const logEntries = [{ textPayload: "build log 1" }];
listBuildsStub.resolves(builds);
listEntriesStub.resolves(logEntries);
const result = await fetch_logs.fn({ buildLogs: true, backendId, location }, {
projectId,
} as any);
expect(listBuildsStub).to.be.calledWith(projectId, location, backendId);
expect(listEntriesStub).to.be.calledOnce;
expect(listEntriesStub.args[0][1]).to.include('resource.labels.build_id="12345"');
expect(result).to.deep.equal(toContent(logEntries));
});
it("should return 'No logs found.' if no build logs are available", async () => {
listBuildsStub.resolves(builds);
listEntriesStub.resolves([]);
const result = await fetch_logs.fn({ buildLogs: true, backendId, location }, {
projectId,
} as any);
expect(result).to.deep.equal(toContent("No logs found."));
});
it("should throw FirebaseError if build ID cannot be determined from buildLogsUri", async () => {
const buildWithInvalidUri = {
createTime: new Date().toISOString(),
buildLogsUri: "invalid-uri",
};
const buildsWithInvalidUri = { builds: [buildWithInvalidUri] };
listBuildsStub.resolves(buildsWithInvalidUri);
await expect(
fetch_logs.fn({ buildLogs: true, backendId, location }, { projectId } as any),
).to.be.rejectedWith(FirebaseError, "Unable to determine the build ID.");
});
}); |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { expect } from "chai"; | ||
import * as sinon from "sinon"; | ||
import { list_backends } from "./list_backends"; | ||
import * as apphosting from "../../../gcp/apphosting"; | ||
import { toContent } from "../../util"; | ||
|
||
describe("list_backends tool", () => { | ||
const projectId = "test-project"; | ||
const location = "us-central1"; | ||
const backendId = "test-backend"; | ||
|
||
let listBackendsStub: sinon.SinonStub; | ||
let getTrafficStub: sinon.SinonStub; | ||
let listDomainsStub: sinon.SinonStub; | ||
let parseBackendNameStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
listBackendsStub = sinon.stub(apphosting, "listBackends"); | ||
getTrafficStub = sinon.stub(apphosting, "getTraffic"); | ||
listDomainsStub = sinon.stub(apphosting, "listDomains"); | ||
parseBackendNameStub = sinon.stub(apphosting, "parseBackendName"); | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
it("should return a message when no backends are found", async () => { | ||
listBackendsStub.resolves({ backends: [] }); | ||
|
||
const result = await list_backends.fn({ location }, { projectId } as any); | ||
|
||
expect(listBackendsStub).to.be.calledWith(projectId, location); | ||
expect(result).to.deep.equal( | ||
toContent(`No backends exist for project ${projectId} in ${location}.`), | ||
); | ||
}); | ||
|
||
it("should list backends with traffic and domain info", async () => { | ||
const backend = { name: `projects/${projectId}/locations/${location}/backends/${backendId}` }; | ||
const backends = { backends: [backend] }; | ||
const traffic = { name: "traffic" }; | ||
const domains = [{ name: "domain" }]; | ||
|
||
listBackendsStub.resolves(backends); | ||
parseBackendNameStub.returns({ location, id: backendId }); | ||
getTrafficStub.resolves(traffic); | ||
listDomainsStub.resolves(domains); | ||
|
||
const result = await list_backends.fn({ location }, { projectId } as any); | ||
|
||
expect(listBackendsStub).to.be.calledWith(projectId, location); | ||
expect(parseBackendNameStub).to.be.calledWith(backend.name); | ||
expect(getTrafficStub).to.be.calledWith(projectId, location, backendId); | ||
expect(listDomainsStub).to.be.calledWith(projectId, location, backendId); | ||
|
||
const expectedData = [{ ...backend, traffic, domains }]; | ||
expect(result).to.deep.equal(toContent(expectedData)); | ||
}); | ||
|
||
it("should handle the default location", async () => { | ||
listBackendsStub.resolves({ backends: [] }); | ||
await list_backends.fn({}, { projectId } as any); | ||
expect(listBackendsStub).to.be.calledWith(projectId, "-"); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test setup for
traffic
andgetTrafficStub
is repeated in both tests within this context. You can move this common setup into abeforeEach
block to reduce duplication and improve maintainability.