Skip to content

Commit

Permalink
Merge pull request #111 from huwshimi/handle-no-versions
Browse files Browse the repository at this point in the history
Be defensive about returned version data
  • Loading branch information
huwshimi authored May 8, 2023
2 parents 0895f47 + ddb5e20 commit 278c06f
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 19 deletions.
79 changes: 65 additions & 14 deletions api/tests/test-versions.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,85 @@
import { cachedAPIResponse, jujuUpdateAvailable } from "../versions";
import {
cachedAPIResponse,
jujuUpdateAvailable,
dashboardUpdateAvailable,
} from "../versions";

describe("versions", () => {
// mock fetch: https://juju.is/latest.json
const mockFetch = jest.fn();
mockFetch.mockReturnValue({
json: () => ({
dashboard: "2.8.1",
juju: ["2.8.1", "2.8.0", "2.7.9"],
}),
});
global.fetch = mockFetch;
let fetchSpy: jest.SpyInstance;

beforeAll(() => {
fetchSpy = jest.spyOn(global, "fetch");
});

beforeEach(() => {
// invalidate the cache
cachedAPIResponse?.fetchedAt.setTime(0);
// mock fetch: https://juju.is/latest.json
fetchSpy.mockImplementation(
jest.fn(() =>
Promise.resolve({
json: () => ({
dashboard: "2.8.1",
juju: ["2.8.1", "2.8.0", "2.7.9"],
}),
})
) as jest.Mock
);
});

afterEach(() => {
jest.resetAllMocks();
});

afterAll(() => {
jest.restoreAllMocks();
});

it("should return true if the Juju controller version is old", async () => {
expect(await jujuUpdateAvailable("2.7.8")).toBe(true);
});

it("should return false if the Juju controller version is new", async () => {
expect(await jujuUpdateAvailable("2.8.1")).toBe(false);
});

it("should return true if the Juju dashboard version is old", async () => {
expect(await jujuUpdateAvailable("2.2.0")).toBe(true);
expect(await dashboardUpdateAvailable("2.2.0")).toBe(true);
});

it("should return false if the Juju dashboard version is new", async () => {
expect(await jujuUpdateAvailable("2.8.1")).toBe(false);
expect(await dashboardUpdateAvailable("2.8.1")).toBe(false);
});

it("handles no juju version response", async () => {
fetchSpy.mockImplementation(
jest.fn(() =>
Promise.resolve({
json: () => ({}),
})
) as jest.Mock
);
expect(await jujuUpdateAvailable("2.8.1")).toBeNull();
});

it("handles no dashboard version response", async () => {
fetchSpy.mockImplementation(
jest.fn(() =>
Promise.resolve({
json: () => ({}),
})
) as jest.Mock
);
expect(await dashboardUpdateAvailable("2.2.0")).toBeNull();
});

it("should use TTL to cache the response", async () => {
await jujuUpdateAvailable("2.7.8");
await jujuUpdateAvailable("2.7.8");
expect(mockFetch).toHaveBeenCalledTimes(1);
expect(fetchSpy).toHaveBeenCalledTimes(1);
// invalidate the cache
cachedAPIResponse?.fetchedAt.setTime(0);
await jujuUpdateAvailable("2.7.8");
expect(mockFetch).toHaveBeenCalledTimes(2);
expect(fetchSpy).toHaveBeenCalledTimes(2);
});
});
17 changes: 13 additions & 4 deletions api/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const versionGreaterThan = (v1: string, v2: string): boolean => {
};

const sortVersions = (versions: string[]): string[] =>
versions.sort((v1, v2) => {
versions?.sort((v1, v2) => {
if (versionGreaterThan(v1, v2)) {
return -1;
} else if (versionGreaterThan(v2, v1)) {
Expand Down Expand Up @@ -49,7 +49,10 @@ const fetchVersions = async () => {
return cachedAPIResponse.data;
cachedAPIResponse = null;
const response = await fetch("https://juju.is/latest.json");
const data = await response.json();
const data: APIPayload | undefined = await response?.json();
if (!data) {
return null;
}
cachedAPIResponse = {
data: { ...data, juju: sortVersions(data.juju) },
fetchedAt: new Date(),
Expand All @@ -64,7 +67,10 @@ const fetchVersions = async () => {
* @returns
*/
export const jujuUpdateAvailable = async (jujuVersion: string) => {
const jujuVersions = (await fetchVersions()).juju;
const jujuVersions = (await fetchVersions())?.juju;
if (!jujuVersions) {
return null;
}
const latestAvailableVersion = jujuVersions.slice(-1)[0];
return versionGreaterThan(latestAvailableVersion, jujuVersion);
};
Expand All @@ -76,6 +82,9 @@ export const jujuUpdateAvailable = async (jujuVersion: string) => {
* @returns
*/
export const dashboardUpdateAvailable = async (dashboardVersion: string) => {
const latestDashboardVersion = (await fetchVersions()).dashboard;
const latestDashboardVersion = (await fetchVersions())?.dashboard;
if (!latestDashboardVersion) {
return null;
}
return versionGreaterThan(latestDashboardVersion, dashboardVersion);
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@canonical/jujulib",
"version": "3.2.0",
"version": "3.2.1",
"description": "Juju API client",
"main": "dist/api/client.js",
"types": "dist/api/client.d.ts",
Expand Down

0 comments on commit 278c06f

Please sign in to comment.