484 lines
15 KiB
JavaScript
484 lines
15 KiB
JavaScript
import { GlAlert, GlLoadingIcon, GlBanner, GlTabs, GlTab } from '@gitlab/ui';
|
|
import { shallowMount } from '@vue/test-utils';
|
|
import VueApollo from 'vue-apollo';
|
|
import Vue, { nextTick } from 'vue';
|
|
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
|
|
import AgentTable from '~/clusters_list/components/agent_table.vue';
|
|
import Agents from '~/clusters_list/components/agents.vue';
|
|
import {
|
|
AGENT_FEEDBACK_KEY,
|
|
AGENT_FEEDBACK_ISSUE,
|
|
KAS_DISABLED_ERROR,
|
|
} from '~/clusters_list/constants';
|
|
import getAgentsQuery from 'ee_else_ce/clusters_list/graphql/queries/get_agents.query.graphql';
|
|
import getSharedAgentsQuery from 'ee_else_ce/clusters_list/graphql/queries/get_shared_agents.query.graphql';
|
|
import getTreeListQuery from '~/clusters_list/graphql/queries/get_tree_list.query.graphql';
|
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
|
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
|
import {
|
|
clusterAgentsResponse,
|
|
clusterAgentsGroupResponse,
|
|
treeListResponseData,
|
|
expectedAgentsList,
|
|
sharedAgentsResponse,
|
|
} from 'ee_else_ce_jest/clusters_list/components/mock_data';
|
|
|
|
Vue.use(VueApollo);
|
|
|
|
describe('Agents', () => {
|
|
let wrapper;
|
|
|
|
const provideData = {
|
|
fullPath: 'path/to/project/group',
|
|
};
|
|
|
|
const projectId = 'gid://gitlab/Project/1';
|
|
|
|
const emptyAgentsResponse = {
|
|
data: {
|
|
project: {
|
|
id: projectId,
|
|
clusterAgents: { nodes: [], count: 0 },
|
|
},
|
|
},
|
|
};
|
|
|
|
const emptySharedAgentsResponse = {
|
|
data: {
|
|
project: {
|
|
id: projectId,
|
|
ciAccessAuthorizedAgents: { nodes: [] },
|
|
userAccessAuthorizedAgents: { nodes: [] },
|
|
},
|
|
},
|
|
};
|
|
|
|
const emptyTreeListResponse = {
|
|
data: {
|
|
project: {
|
|
id: projectId,
|
|
repository: {
|
|
tree: {
|
|
trees: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const createWrapper = async ({
|
|
props = {},
|
|
glFeatures = {},
|
|
isGroup = false,
|
|
agentQueryResponse = jest.fn().mockResolvedValue(clusterAgentsResponse),
|
|
treeListQueryResponse = jest.fn().mockResolvedValue(emptyTreeListResponse),
|
|
sharedAgentsQueryResponse = jest.fn().mockResolvedValue(emptySharedAgentsResponse),
|
|
slots,
|
|
} = {}) => {
|
|
const apolloProvider = createMockApollo(
|
|
[
|
|
[getAgentsQuery, agentQueryResponse],
|
|
[getSharedAgentsQuery, sharedAgentsQueryResponse],
|
|
[getTreeListQuery, treeListQueryResponse],
|
|
],
|
|
{},
|
|
{ typePolicies: { Query: { fields: { project: { merge: true } } } } },
|
|
);
|
|
|
|
wrapper = shallowMount(Agents, {
|
|
apolloProvider,
|
|
propsData: {
|
|
...props,
|
|
},
|
|
provide: {
|
|
...provideData,
|
|
isGroup,
|
|
glFeatures,
|
|
},
|
|
stubs: {
|
|
GlBanner,
|
|
LocalStorageSync,
|
|
},
|
|
slots,
|
|
});
|
|
|
|
await nextTick();
|
|
await waitForPromises();
|
|
};
|
|
|
|
const findAgentTabs = () => wrapper.findComponent(GlTabs);
|
|
const findTab = () => wrapper.findAllComponents(GlTab);
|
|
const findAgentTable = () => wrapper.findComponent(AgentTable);
|
|
const findEmptyState = () => wrapper.findComponent(AgentEmptyState);
|
|
const findAlert = () => wrapper.findComponent(GlAlert);
|
|
const findBanner = () => wrapper.findComponent(GlBanner);
|
|
|
|
const getAllTabTitles = () => findTab().wrappers.map((tab) => tab.attributes('title'));
|
|
|
|
afterEach(() => {
|
|
localStorage.removeItem(AGENT_FEEDBACK_KEY);
|
|
});
|
|
|
|
describe('when there is a list of agents', () => {
|
|
it('should not render empty state', async () => {
|
|
await createWrapper();
|
|
|
|
expect(findEmptyState().exists()).toBe(false);
|
|
});
|
|
|
|
it('should render agent tabs', async () => {
|
|
await createWrapper();
|
|
|
|
expect(findAgentTabs().exists()).toBe(true);
|
|
});
|
|
|
|
it('should render agent table', async () => {
|
|
await createWrapper();
|
|
|
|
expect(findAgentTable().exists()).toBe(true);
|
|
});
|
|
|
|
it('should pass agent and folder info to table component', async () => {
|
|
await createWrapper({
|
|
treeListQueryResponse: jest.fn().mockResolvedValue(treeListResponseData),
|
|
});
|
|
|
|
expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList);
|
|
});
|
|
|
|
it('should emit agents count to the parent component', async () => {
|
|
await createWrapper();
|
|
|
|
expect(wrapper.emitted().onAgentsLoad).toEqual([[expectedAgentsList.length]]);
|
|
});
|
|
|
|
it('should render a slot for alerts if provided', async () => {
|
|
await createWrapper({ slots: { alerts: 'slotContent' } });
|
|
|
|
expect(wrapper.text()).toContain('slotContent');
|
|
});
|
|
|
|
describe.each`
|
|
featureFlagEnabled | localStorageItemExists | bannerShown
|
|
${true} | ${false} | ${true}
|
|
${true} | ${true} | ${false}
|
|
${false} | ${true} | ${false}
|
|
${false} | ${false} | ${false}
|
|
`(
|
|
'when the feature flag enabled is $featureFlagEnabled and dismissed localStorage item exists is $localStorageItemExists',
|
|
({ featureFlagEnabled, localStorageItemExists, bannerShown }) => {
|
|
const glFeatures = {
|
|
showGitlabAgentFeedback: featureFlagEnabled,
|
|
};
|
|
beforeEach(() => {
|
|
if (localStorageItemExists) {
|
|
localStorage.setItem(AGENT_FEEDBACK_KEY, true);
|
|
}
|
|
|
|
return createWrapper({ glFeatures });
|
|
});
|
|
|
|
it(`should ${bannerShown ? 'show' : 'hide'} the feedback banner`, () => {
|
|
expect(findBanner().exists()).toBe(bannerShown);
|
|
});
|
|
},
|
|
);
|
|
|
|
describe('when the agent feedback banner is present', () => {
|
|
const glFeatures = {
|
|
showGitlabAgentFeedback: true,
|
|
};
|
|
beforeEach(() => {
|
|
return createWrapper({ glFeatures });
|
|
});
|
|
|
|
it('should render the correct title', () => {
|
|
expect(findBanner().props('title')).toBe('Tell us what you think');
|
|
});
|
|
|
|
it('should render the correct issue link', () => {
|
|
expect(findBanner().props('buttonLink')).toBe(AGENT_FEEDBACK_ISSUE);
|
|
});
|
|
});
|
|
|
|
describe('agent tabs', () => {
|
|
it('should render project agents tab when the agents query has returned data', async () => {
|
|
await createWrapper();
|
|
|
|
expect(findTab().at(0).attributes('title')).toBe('Project agents');
|
|
});
|
|
|
|
it('should render project agents tab with alert when the agents query has errored', async () => {
|
|
await createWrapper({ agentQueryResponse: jest.fn().mockRejectedValue({}) });
|
|
|
|
expect(findTab().at(0).attributes('title')).toBe('Project agents');
|
|
expect(findTab().at(0).text()).toBe('An error occurred while loading your agents');
|
|
});
|
|
|
|
it('should not render shared agents tab when the query has not returned data', async () => {
|
|
await createWrapper();
|
|
|
|
expect(findTab()).toHaveLength(1);
|
|
});
|
|
|
|
it('should render shared agents tab when the query has returned data', async () => {
|
|
await createWrapper({
|
|
sharedAgentsQueryResponse: jest.fn().mockResolvedValue(sharedAgentsResponse),
|
|
});
|
|
|
|
expect(findTab()).toHaveLength(2);
|
|
expect(findTab().at(1).attributes('title')).toBe('Shared agents');
|
|
});
|
|
|
|
it('should render shared agents tab with alert when the agents query has errored', async () => {
|
|
await createWrapper({ sharedAgentsQueryResponse: jest.fn().mockRejectedValue({}) });
|
|
|
|
expect(findTab().at(1).attributes('title')).toBe('Shared agents');
|
|
expect(findTab().at(1).text()).toBe('An error occurred while loading your agents');
|
|
});
|
|
|
|
it('should render configurations tab when the query has returned data', async () => {
|
|
await createWrapper({
|
|
treeListQueryResponse: jest.fn().mockResolvedValue(treeListResponseData),
|
|
});
|
|
|
|
expect(findTab()).toHaveLength(2);
|
|
expect(findTab().at(1).attributes('title')).toBe('Available configurations');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('sharedAgentsList computed property', () => {
|
|
const ciAccessAgent = sharedAgentsResponse.data.project.ciAccessAuthorizedAgents.nodes[0];
|
|
const userAccessAgent = sharedAgentsResponse.data.project.userAccessAuthorizedAgents.nodes[0];
|
|
|
|
const createSharedAgentsResponse = (ciAgents, userAgents) => ({
|
|
data: {
|
|
project: {
|
|
id: projectId,
|
|
ciAccessAuthorizedAgents: { nodes: ciAgents },
|
|
userAccessAuthorizedAgents: { nodes: userAgents },
|
|
},
|
|
},
|
|
});
|
|
|
|
it('filters out agents from the same project', async () => {
|
|
const sameProjectAgent = {
|
|
agent: {
|
|
...userAccessAgent.agent,
|
|
project: {
|
|
id: projectId,
|
|
fullPath: provideData.fullPath,
|
|
webUrl: `https://gdl.test/${provideData.fullPath}`,
|
|
},
|
|
},
|
|
};
|
|
|
|
const updatedResponse = createSharedAgentsResponse([ciAccessAgent], [sameProjectAgent]);
|
|
|
|
await createWrapper({
|
|
sharedAgentsQueryResponse: jest.fn().mockResolvedValue(updatedResponse),
|
|
});
|
|
|
|
expect(findTab()).toHaveLength(2);
|
|
expect(findTab().at(1).attributes('title')).toBe('Shared agents');
|
|
|
|
expect(findTab().at(1).findComponent(AgentTable).props('agents')).toHaveLength(1);
|
|
});
|
|
|
|
it('filters out agents duplicates', async () => {
|
|
const updatedResponse = createSharedAgentsResponse(
|
|
[ciAccessAgent],
|
|
[ciAccessAgent, userAccessAgent],
|
|
);
|
|
|
|
await createWrapper({
|
|
sharedAgentsQueryResponse: jest.fn().mockResolvedValue(updatedResponse),
|
|
});
|
|
|
|
expect(findTab()).toHaveLength(2);
|
|
expect(findTab().at(1).attributes('title')).toBe('Shared agents');
|
|
|
|
expect(findTab().at(1).findComponent(AgentTable).props('agents')).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('agent list update', () => {
|
|
const initialResponse = { ...clusterAgentsResponse };
|
|
const newAgent = {
|
|
...clusterAgentsResponse.data.project.clusterAgents.nodes[0],
|
|
id: 'gid://gitlab/Clusters::Agent/999',
|
|
};
|
|
const updatedResponse = {
|
|
data: {
|
|
project: {
|
|
...clusterAgentsResponse.data.project,
|
|
clusterAgents: {
|
|
nodes: [...clusterAgentsResponse.data.project.clusterAgents.nodes, newAgent],
|
|
count: 3,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
beforeEach(() => {
|
|
const agentQueryResponse = jest
|
|
.fn()
|
|
.mockResolvedValueOnce(initialResponse)
|
|
.mockResolvedValueOnce(updatedResponse);
|
|
|
|
return createWrapper({ agentQueryResponse });
|
|
});
|
|
|
|
it('should update the agent table when query data changes', async () => {
|
|
expect(findAgentTable().props('agents')).toHaveLength(expectedAgentsList.length);
|
|
|
|
await wrapper.vm.$apollo.queries.agents.refetch();
|
|
await waitForPromises();
|
|
|
|
expect(findAgentTable().props('agents')).toHaveLength(expectedAgentsList.length + 1);
|
|
});
|
|
|
|
it('should navigate to the project agents tab', async () => {
|
|
// The project agents tab is opened by default
|
|
expect(findAgentTabs().props('value')).toBe(0);
|
|
|
|
// Open the second tab
|
|
findAgentTabs().vm.$emit('input', 1);
|
|
await waitForPromises();
|
|
expect(findAgentTabs().props('value')).toBe(1);
|
|
|
|
// On data change, go back to the project agents tab
|
|
await wrapper.vm.$apollo.queries.agents.refetch();
|
|
await waitForPromises();
|
|
expect(findAgentTabs().props('value')).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('when the agent list and configuration list are empty', () => {
|
|
beforeEach(() => {
|
|
return createWrapper({
|
|
agentQueryResponse: jest.fn().mockResolvedValue(emptyAgentsResponse),
|
|
});
|
|
});
|
|
|
|
it('should render empty state', () => {
|
|
expect(findAgentTable().exists()).toBe(false);
|
|
expect(findEmptyState().exists()).toBe(true);
|
|
});
|
|
|
|
it('should not show agent feedback alert', () => {
|
|
expect(findAlert().exists()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('when agents query has errored', () => {
|
|
it('displays an alert message', async () => {
|
|
await createWrapper({
|
|
agentQueryResponse: jest.fn().mockRejectedValue({}),
|
|
});
|
|
|
|
expect(findAlert().text()).toBe('An error occurred while loading your agents');
|
|
});
|
|
|
|
it('emits `kasDisabled` event if the error is related to KAS being disabled', async () => {
|
|
const error = new Error(KAS_DISABLED_ERROR);
|
|
await createWrapper({
|
|
agentQueryResponse: jest.fn().mockRejectedValue(error),
|
|
});
|
|
|
|
expect(wrapper.emitted().kasDisabled).toEqual([[true]]);
|
|
});
|
|
});
|
|
|
|
describe('when agents query is loading', () => {
|
|
beforeEach(() => {
|
|
createWrapper({
|
|
agentQueryResponse: jest.fn().mockReturnValue(new Promise(() => {})),
|
|
});
|
|
});
|
|
|
|
it('displays a loading icon', () => {
|
|
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('when on group page', () => {
|
|
const agentQueryResponse = jest.fn().mockReturnValue(new Promise(() => {}));
|
|
const sharedAgentsQueryResponse = jest.fn().mockReturnValue(new Promise(() => {}));
|
|
const treeListQueryResponse = jest.fn().mockReturnValue(new Promise(() => {}));
|
|
|
|
describe('request data', () => {
|
|
beforeEach(() => {
|
|
return createWrapper({
|
|
isGroup: true,
|
|
agentQueryResponse,
|
|
sharedAgentsQueryResponse,
|
|
treeListQueryResponse,
|
|
});
|
|
});
|
|
|
|
it('should request agents with correct variables', () => {
|
|
expect(agentQueryResponse).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
fullPath: 'path/to/project/group',
|
|
isGroup: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should skip sharedAgents query', () => {
|
|
expect(sharedAgentsQueryResponse).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should skip treeList query', () => {
|
|
expect(treeListQueryResponse).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('agent tabs', () => {
|
|
it('should render group agents tab when the agents query has returned data', async () => {
|
|
await createWrapper({
|
|
isGroup: true,
|
|
agentQueryResponse: jest.fn().mockResolvedValue(clusterAgentsGroupResponse),
|
|
});
|
|
|
|
expect(findTab().at(0).attributes('title')).toBe('Group agents');
|
|
});
|
|
|
|
it('should render group agents tab with alert when the agents query has errored', async () => {
|
|
await createWrapper({
|
|
isGroup: true,
|
|
agentQueryResponse: jest.fn().mockRejectedValue({}),
|
|
});
|
|
|
|
expect(findTab().at(0).attributes('title')).toBe('Group agents');
|
|
expect(findTab().at(0).text()).toBe('An error occurred while loading your agents');
|
|
});
|
|
|
|
it('should not render shared agents tab', async () => {
|
|
await createWrapper({
|
|
isGroup: true,
|
|
agentQueryResponse: jest.fn().mockResolvedValue(clusterAgentsGroupResponse),
|
|
sharedAgentsQueryResponse: jest.fn().mockRejectedValue({}),
|
|
});
|
|
|
|
expect(findTab()).toHaveLength(1);
|
|
expect(getAllTabTitles()).not.toContain('Shared agents');
|
|
});
|
|
|
|
it('should not render configurations tab', async () => {
|
|
await createWrapper({
|
|
isGroup: true,
|
|
});
|
|
|
|
expect(findTab()).toHaveLength(1);
|
|
expect(getAllTabTitles()).not.toContain('Available configurations');
|
|
});
|
|
});
|
|
});
|
|
});
|