2021-11-18 22:05:49 +05:30
|
|
|
import { GlLink } from '@gitlab/ui';
|
2021-09-04 01:27:46 +05:30
|
|
|
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
|
|
|
|
import VueApollo from 'vue-apollo';
|
|
|
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
2021-10-27 15:23:28 +05:30
|
|
|
import setWindowLocation from 'helpers/set_window_location_helper';
|
2021-11-11 11:23:49 +05:30
|
|
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
2021-09-04 01:27:46 +05:30
|
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
2021-09-30 23:02:18 +05:30
|
|
|
import createFlash from '~/flash';
|
2021-11-18 22:05:49 +05:30
|
|
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
2021-09-04 01:27:46 +05:30
|
|
|
import { updateHistory } from '~/lib/utils/url_utility';
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
|
2021-12-11 22:18:48 +05:30
|
|
|
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
|
2021-09-04 01:27:46 +05:30
|
|
|
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
|
|
|
|
import RunnerList from '~/runner/components/runner_list.vue';
|
2021-12-11 22:18:48 +05:30
|
|
|
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
|
2021-09-04 01:27:46 +05:30
|
|
|
import RunnerPagination from '~/runner/components/runner_pagination.vue';
|
|
|
|
|
|
|
|
import {
|
2021-11-11 11:23:49 +05:30
|
|
|
ADMIN_FILTERED_SEARCH_NAMESPACE,
|
2021-09-04 01:27:46 +05:30
|
|
|
CREATED_ASC,
|
|
|
|
CREATED_DESC,
|
|
|
|
DEFAULT_SORT,
|
|
|
|
INSTANCE_TYPE,
|
|
|
|
PARAM_KEY_STATUS,
|
2021-11-11 11:23:49 +05:30
|
|
|
PARAM_KEY_TAG,
|
2021-09-04 01:27:46 +05:30
|
|
|
STATUS_ACTIVE,
|
|
|
|
RUNNER_PAGE_SIZE,
|
|
|
|
} from '~/runner/constants';
|
|
|
|
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
|
2021-09-30 23:02:18 +05:30
|
|
|
import { captureException } from '~/runner/sentry_utils';
|
2021-11-11 11:23:49 +05:30
|
|
|
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
import { runnersData, runnersDataPaginated } from '../mock_data';
|
|
|
|
|
|
|
|
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
|
2021-12-11 22:18:48 +05:30
|
|
|
const mockActiveRunnersCount = '2';
|
|
|
|
const mockAllRunnersCount = '6';
|
|
|
|
const mockInstanceRunnersCount = '3';
|
|
|
|
const mockGroupRunnersCount = '2';
|
|
|
|
const mockProjectRunnersCount = '1';
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-09-30 23:02:18 +05:30
|
|
|
jest.mock('~/flash');
|
|
|
|
jest.mock('~/runner/sentry_utils');
|
2021-09-04 01:27:46 +05:30
|
|
|
jest.mock('~/lib/utils/url_utility', () => ({
|
|
|
|
...jest.requireActual('~/lib/utils/url_utility'),
|
|
|
|
updateHistory: jest.fn(),
|
|
|
|
}));
|
|
|
|
|
|
|
|
const localVue = createLocalVue();
|
|
|
|
localVue.use(VueApollo);
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
describe('AdminRunnersApp', () => {
|
2021-09-04 01:27:46 +05:30
|
|
|
let wrapper;
|
|
|
|
let mockRunnersQuery;
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
|
|
|
|
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
|
2021-09-04 01:27:46 +05:30
|
|
|
const findRunnerList = () => wrapper.findComponent(RunnerList);
|
2021-11-11 11:23:49 +05:30
|
|
|
const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination));
|
|
|
|
const findRunnerPaginationPrev = () =>
|
|
|
|
findRunnerPagination().findByLabelText('Go to previous page');
|
|
|
|
const findRunnerPaginationNext = () => findRunnerPagination().findByLabelText('Go to next page');
|
2021-09-04 01:27:46 +05:30
|
|
|
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
|
2021-11-11 11:23:49 +05:30
|
|
|
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
|
2021-09-04 01:27:46 +05:30
|
|
|
const handlers = [[getRunnersQuery, mockRunnersQuery]];
|
|
|
|
|
2021-10-27 15:23:28 +05:30
|
|
|
wrapper = mountFn(AdminRunnersApp, {
|
2021-09-04 01:27:46 +05:30
|
|
|
localVue,
|
|
|
|
apolloProvider: createMockApollo(handlers),
|
|
|
|
propsData: {
|
|
|
|
registrationToken: mockRegistrationToken,
|
2021-12-11 22:18:48 +05:30
|
|
|
activeRunnersCount: mockActiveRunnersCount,
|
|
|
|
allRunnersCount: mockAllRunnersCount,
|
|
|
|
instanceRunnersCount: mockInstanceRunnersCount,
|
|
|
|
groupRunnersCount: mockGroupRunnersCount,
|
|
|
|
projectRunnersCount: mockProjectRunnersCount,
|
2021-09-04 01:27:46 +05:30
|
|
|
...props,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
2021-10-27 15:23:28 +05:30
|
|
|
setWindowLocation('/admin/runners');
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
mockRunnersQuery = jest.fn().mockResolvedValue(runnersData);
|
2021-11-11 11:23:49 +05:30
|
|
|
createComponent();
|
2021-09-04 01:27:46 +05:30
|
|
|
await waitForPromises();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
mockRunnersQuery.mockReset();
|
|
|
|
wrapper.destroy();
|
|
|
|
});
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
it('shows the runner tabs with a runner count', async () => {
|
|
|
|
createComponent({ mountFn: mount });
|
|
|
|
|
|
|
|
await waitForPromises();
|
|
|
|
|
|
|
|
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
|
|
|
|
`All ${mockAllRunnersCount} Instance ${mockInstanceRunnersCount} Group ${mockGroupRunnersCount} Project ${mockProjectRunnersCount}`,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it('shows the runner setup instructions', () => {
|
2021-12-11 22:18:48 +05:30
|
|
|
expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
|
|
|
|
expect(findRegistrationDropdown().props('type')).toBe(INSTANCE_TYPE);
|
2021-11-11 11:23:49 +05:30
|
|
|
});
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
it('shows the runners list', () => {
|
2021-11-11 11:23:49 +05:30
|
|
|
expect(findRunnerList().props('runners')).toEqual(runnersData.data.runners.nodes);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
2021-11-18 22:05:49 +05:30
|
|
|
it('runner item links to the runner admin page', async () => {
|
|
|
|
createComponent({ mountFn: mount });
|
|
|
|
|
|
|
|
await waitForPromises();
|
|
|
|
|
|
|
|
const { id, shortSha } = runnersData.data.runners.nodes[0];
|
|
|
|
const numericId = getIdFromGraphQLId(id);
|
|
|
|
|
|
|
|
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
|
|
|
|
|
|
|
|
expect(runnerLink.text()).toBe(`#${numericId} (${shortSha})`);
|
|
|
|
expect(runnerLink.attributes('href')).toBe(`http://localhost/admin/runners/${numericId}`);
|
|
|
|
});
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
it('requests the runners with no filters', () => {
|
|
|
|
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
|
|
|
|
status: undefined,
|
|
|
|
type: undefined,
|
|
|
|
sort: DEFAULT_SORT,
|
|
|
|
first: RUNNER_PAGE_SIZE,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it('sets tokens in the filtered search', () => {
|
|
|
|
createComponent({ mountFn: mount });
|
|
|
|
|
|
|
|
expect(findFilteredSearch().props('tokens')).toEqual([
|
|
|
|
expect.objectContaining({
|
|
|
|
type: PARAM_KEY_STATUS,
|
|
|
|
options: expect.any(Array),
|
|
|
|
}),
|
|
|
|
expect.objectContaining({
|
|
|
|
type: PARAM_KEY_TAG,
|
|
|
|
recentTokenValuesStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
|
|
|
|
}),
|
|
|
|
]);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it('shows the active runner count', () => {
|
|
|
|
createComponent({ mountFn: mount });
|
|
|
|
|
|
|
|
expect(findRunnerFilteredSearchBar().text()).toMatch(
|
|
|
|
`Runners currently online: ${mockActiveRunnersCount}`,
|
|
|
|
);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('when a filter is preselected', () => {
|
|
|
|
beforeEach(async () => {
|
2021-10-27 15:23:28 +05:30
|
|
|
setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`);
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
createComponent();
|
2021-09-04 01:27:46 +05:30
|
|
|
await waitForPromises();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('sets the filters in the search bar', () => {
|
|
|
|
expect(findRunnerFilteredSearchBar().props('value')).toEqual({
|
2021-12-11 22:18:48 +05:30
|
|
|
runnerType: INSTANCE_TYPE,
|
2021-09-04 01:27:46 +05:30
|
|
|
filters: [
|
|
|
|
{ type: 'status', value: { data: STATUS_ACTIVE, operator: '=' } },
|
2021-09-30 23:02:18 +05:30
|
|
|
{ type: 'tag', value: { data: 'tag1', operator: '=' } },
|
2021-09-04 01:27:46 +05:30
|
|
|
],
|
|
|
|
sort: 'CREATED_DESC',
|
|
|
|
pagination: { page: 1 },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('requests the runners with filter parameters', () => {
|
|
|
|
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
|
|
|
|
status: STATUS_ACTIVE,
|
|
|
|
type: INSTANCE_TYPE,
|
2021-09-30 23:02:18 +05:30
|
|
|
tagList: ['tag1'],
|
2021-09-04 01:27:46 +05:30
|
|
|
sort: DEFAULT_SORT,
|
|
|
|
first: RUNNER_PAGE_SIZE,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when a filter is selected by the user', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
findRunnerFilteredSearchBar().vm.$emit('input', {
|
2021-12-11 22:18:48 +05:30
|
|
|
runnerType: null,
|
2021-11-11 11:23:49 +05:30
|
|
|
filters: [{ type: PARAM_KEY_STATUS, value: { data: STATUS_ACTIVE, operator: '=' } }],
|
2021-09-04 01:27:46 +05:30
|
|
|
sort: CREATED_ASC,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('updates the browser url', () => {
|
|
|
|
expect(updateHistory).toHaveBeenLastCalledWith({
|
|
|
|
title: expect.any(String),
|
2021-09-30 23:02:18 +05:30
|
|
|
url: 'http://test.host/admin/runners?status[]=ACTIVE&sort=CREATED_ASC',
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('requests the runners with filters', () => {
|
|
|
|
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
|
|
|
|
status: STATUS_ACTIVE,
|
|
|
|
sort: CREATED_ASC,
|
|
|
|
first: RUNNER_PAGE_SIZE,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it('when runners have not loaded, shows a loading state', () => {
|
|
|
|
createComponent();
|
|
|
|
expect(findRunnerList().props('loading')).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
describe('when no runners are found', () => {
|
|
|
|
beforeEach(async () => {
|
2021-11-11 11:23:49 +05:30
|
|
|
mockRunnersQuery = jest.fn().mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runners: { nodes: [] },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
createComponent();
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('shows a message for no results', async () => {
|
|
|
|
expect(wrapper.text()).toContain('No runners found');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('when runners query fails', () => {
|
2021-11-11 11:23:49 +05:30
|
|
|
beforeEach(() => {
|
2021-09-30 23:02:18 +05:30
|
|
|
mockRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!'));
|
2021-11-11 11:23:49 +05:30
|
|
|
createComponent();
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
it('error is shown to the user', async () => {
|
|
|
|
expect(createFlash).toHaveBeenCalledTimes(1);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', async () => {
|
2021-09-30 23:02:18 +05:30
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
|
|
|
error: new Error('Network error: Error!'),
|
2021-10-27 15:23:28 +05:30
|
|
|
component: 'AdminRunnersApp',
|
2021-09-30 23:02:18 +05:30
|
|
|
});
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
describe('Pagination', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
mockRunnersQuery = jest.fn().mockResolvedValue(runnersDataPaginated);
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
createComponent({ mountFn: mount });
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('more pages can be selected', () => {
|
|
|
|
expect(findRunnerPagination().text()).toMatchInterpolatedText('Prev Next');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('cannot navigate to the previous page', () => {
|
2021-11-11 11:23:49 +05:30
|
|
|
expect(findRunnerPaginationPrev().attributes('aria-disabled')).toBe('true');
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('navigates to the next page', async () => {
|
2021-11-11 11:23:49 +05:30
|
|
|
await findRunnerPaginationNext().trigger('click');
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
|
|
|
|
sort: CREATED_DESC,
|
|
|
|
first: RUNNER_PAGE_SIZE,
|
|
|
|
after: runnersDataPaginated.data.runners.pageInfo.endCursor,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|