mirror of
https://github.com/prymitive/karma
synced 2026-05-11 03:46:48 +00:00
Merge pull request #60 from prymitive/ui-tests
More test coverage for the UI code
This commit is contained in:
4
Makefile
4
Makefile
@@ -132,7 +132,9 @@ test-js-watch: .build/deps-build-node.ok
|
||||
@# hitting issues with jest --watch due to
|
||||
@# https://github.com/facebook/jest/issues/3436
|
||||
@# use onchange for now
|
||||
cd ui && ./node_modules/onchange/cli.js 'src/*.js' 'src/**/*.js' -- npm test -- --coverage
|
||||
cd ui && ./node_modules/onchange/cli.js 'src/*.js' 'src/**/*.js' -- \
|
||||
npm test -- \
|
||||
--coverage --coverageReporters=lcov --onlyChanged
|
||||
|
||||
.PHONY: test
|
||||
test: lint test-go test-js
|
||||
|
||||
@@ -192,31 +192,32 @@ class AlertStore {
|
||||
this.filters.setFilters(initialFilters);
|
||||
}
|
||||
|
||||
// fetch is throttled to once per 500ms
|
||||
fetch = action(
|
||||
throttle(() => {
|
||||
this.status.setInProgress();
|
||||
fetch = action(() => {
|
||||
this.status.setInProgress();
|
||||
|
||||
const alertsURI =
|
||||
FormatUnseeBackendURI("alerts.json?") +
|
||||
FormatAPIFilterQuery(this.filters.values.map(f => f.raw));
|
||||
const alertsURI =
|
||||
FormatUnseeBackendURI("alerts.json?") +
|
||||
FormatAPIFilterQuery(this.filters.values.map(f => f.raw));
|
||||
|
||||
fetch(alertsURI)
|
||||
.then(result => result.json())
|
||||
.then(result => {
|
||||
this.parseAPIResponse(result);
|
||||
})
|
||||
.catch(err =>
|
||||
this.handleFetchError(
|
||||
`Request for ${alertsURI} failed with "${err.message}"`
|
||||
)
|
||||
return fetch(alertsURI)
|
||||
.then(result => result.json())
|
||||
.then(result => {
|
||||
return this.parseAPIResponse(result);
|
||||
})
|
||||
.catch(err => {
|
||||
console.trace(err);
|
||||
return this.handleFetchError(
|
||||
`Request for ${alertsURI} failed with "${err.message}"`
|
||||
);
|
||||
}, 500)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
fetchWithThrottle = throttle(this.fetch, 500);
|
||||
|
||||
parseAPIResponse = action(result => {
|
||||
if (result.error) {
|
||||
this.handleFetchError(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const queryFilters = new Set(
|
||||
@@ -226,7 +227,9 @@ class AlertStore {
|
||||
.sort()
|
||||
);
|
||||
const responseFilters = new Set(result.filters.map(m => m.text).sort());
|
||||
if (JSON.stringify(queryFilters) !== JSON.stringify(responseFilters)) {
|
||||
if (
|
||||
JSON.stringify([...queryFilters]) !== JSON.stringify([...responseFilters])
|
||||
) {
|
||||
console.info(
|
||||
`Got response with filters=${responseFilters} while expecting results for ${queryFilters}, ignoring`
|
||||
);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { ConsoleMock } from "__mocks__/Console";
|
||||
import { FetchMock, EmptyAPIResponse } from "__mocks__/Fetch";
|
||||
|
||||
import {
|
||||
AlertStore,
|
||||
AlertStoreStatuses,
|
||||
@@ -5,6 +8,16 @@ import {
|
||||
DecodeLocationSearch
|
||||
} from "Stores/AlertStore";
|
||||
|
||||
beforeEach(() => {
|
||||
// wipe REACT_APP_BACKEND_URI env on each run as it's used by some tests
|
||||
delete process.env.REACT_APP_BACKEND_URI;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// same after each
|
||||
delete process.env.REACT_APP_BACKEND_URI;
|
||||
});
|
||||
|
||||
describe("AlertStore.status", () => {
|
||||
it("status is initially idle with no error", () => {
|
||||
const store = new AlertStore([]);
|
||||
@@ -116,11 +129,6 @@ describe("AlertStore.filters", () => {
|
||||
});
|
||||
|
||||
describe("FormatUnseeBackendURI", () => {
|
||||
beforeEach(() => {
|
||||
// wipe REACT_APP_BACKEND_URI env on each run as it's used by some tests
|
||||
delete process.env.REACT_APP_BACKEND_URI;
|
||||
});
|
||||
|
||||
it("FormatUnseeBackendURI without REACT_APP_BACKEND_URI env returns ./ prefixed URIs", () => {
|
||||
const uri = FormatUnseeBackendURI("foo/bar");
|
||||
expect(uri).toEqual("./foo/bar");
|
||||
@@ -182,3 +190,88 @@ describe("DecodeLocationSearch", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("AlertStore.fetch", () => {
|
||||
it("parseAPIResponse() rejects a response with mismatched filters", () => {
|
||||
const consoleSpy = ConsoleMock("info");
|
||||
|
||||
const response = EmptyAPIResponse();
|
||||
const store = new AlertStore([]);
|
||||
store.parseAPIResponse(response);
|
||||
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Idle);
|
||||
// there should be no filters set on AlertStore instance since we started
|
||||
// with 0 and rejected response with 1 filter
|
||||
expect(store.filters.values).toHaveLength(0);
|
||||
// console.info should have been called since we emited a warning
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("parseAPIResponse() works for a single filter 'label=value'", () => {
|
||||
const response = EmptyAPIResponse();
|
||||
|
||||
const store = new AlertStore(["label=value"]);
|
||||
store.parseAPIResponse(response);
|
||||
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Idle);
|
||||
expect(store.info.version).toBe("fakeVersion");
|
||||
});
|
||||
|
||||
it("fetch() works with valid response", async () => {
|
||||
const response = EmptyAPIResponse();
|
||||
global.fetch = FetchMock(response);
|
||||
|
||||
const store = new AlertStore(["label=value"]);
|
||||
await expect(store.fetch()).resolves.toBeUndefined();
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Idle);
|
||||
expect(store.info.version).toBe("fakeVersion");
|
||||
|
||||
global.fetch.mockRestore();
|
||||
});
|
||||
|
||||
it("fetch() handles response with error correctly", async () => {
|
||||
global.fetch = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
json: () => ({
|
||||
error: "Fetch error"
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const store = new AlertStore([]);
|
||||
await expect(store.fetch()).resolves.toBeUndefined();
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Failure);
|
||||
expect(store.info.version).toBe("unknown");
|
||||
|
||||
global.fetch.mockRestore();
|
||||
});
|
||||
|
||||
it("fetch() handles response that throws an error correctly", async () => {
|
||||
const consoleSpy = ConsoleMock("trace");
|
||||
global.fetch = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
json: () => {
|
||||
throw new Error("Failed fetch");
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const store = new AlertStore([]);
|
||||
await expect(store.fetch()).resolves.toHaveProperty("error");
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
expect(store.status.value).toEqual(AlertStoreStatuses.Failure);
|
||||
expect(store.info.version).toBe("unknown");
|
||||
// there should be a trace of the error
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
global.fetch.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
4
ui/src/__mocks__/Console.js
Normal file
4
ui/src/__mocks__/Console.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const ConsoleMock = level =>
|
||||
jest.spyOn(console, level).mockImplementation(() => jest.fn());
|
||||
|
||||
export { ConsoleMock };
|
||||
41
ui/src/__mocks__/Fetch.js
Normal file
41
ui/src/__mocks__/Fetch.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import moment from "moment";
|
||||
|
||||
const FetchMock = data =>
|
||||
jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
json: () => data
|
||||
})
|
||||
);
|
||||
|
||||
const EmptyAPIResponse = () => ({
|
||||
status: "success",
|
||||
timestamp: moment().toISOString(),
|
||||
version: "fakeVersion",
|
||||
upstreams: {
|
||||
counters: { total: 1, healthy: 1, failed: 0 },
|
||||
instances: [{ name: "default", uri: "file:///mock", error: "" }]
|
||||
},
|
||||
silences: { default: {} },
|
||||
groups: {},
|
||||
totalAlerts: 0,
|
||||
colors: {},
|
||||
filters: [
|
||||
{
|
||||
text: "label=value",
|
||||
name: "label",
|
||||
matcher: "=",
|
||||
value: "value",
|
||||
hits: 0,
|
||||
isValid: true
|
||||
}
|
||||
],
|
||||
counters: {},
|
||||
settings: {
|
||||
staticColorLabels: ["job"],
|
||||
annotationsDefaultHidden: false,
|
||||
annotationsHidden: [],
|
||||
annotationsVisible: []
|
||||
}
|
||||
});
|
||||
|
||||
export { FetchMock, EmptyAPIResponse };
|
||||
Reference in New Issue
Block a user