Skip to content

Commit

Permalink
Unit test for endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
yoiteyou committed Jan 2, 2025
1 parent 160da8b commit 21950ae
Show file tree
Hide file tree
Showing 8 changed files with 657 additions and 1 deletion.
115 changes: 115 additions & 0 deletions frontend/src/components/__tests__/endpoints/EndpointDetail.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { describe, it, expect, vi } from "vitest";
import { mount } from "@vue/test-utils";
import EndpointDetail from "@/components/endpoints/EndpointDetail.vue";

vi.mock('vue3-cookies', () => ({
useCookies: () => ({
cookies: {
get: vi.fn().mockReturnValue('mocked-token')
}
})
}));

vi.mock('@/packs/useFetchApi', () => {
return {
default: vi.fn((url) => {
return {
json: vi.fn().mockImplementation(() => {
if (url.includes('/models/test-namespace/test-model')) {
return Promise.resolve({
data: {
value: {
data: {
namespace: 'test-namespace',
name: 'test-model',
}
}
}
});
}
if (url.includes('/models/test-namespace/test-model/run/123')) {
return Promise.resolve({
data: {
value: {
data: {
deploy_name: 'Test Endpoint',
endpoint: 'test-endpoint.com',
status: 'Running',
hardware: 'test-hardware',
actual_replica: 2,
cluster_id: 'cluster-1',
sku: 'test-sku',
model_id: 'test-model',
private: false,
repository_id: 'repo-1',
deploy_id: 123
}
}
}
});
}
if (url.includes('/tags')) {
return Promise.resolve({
data: {
value: []
},
error: { value: null }
});
}
})
};
})
};
});

vi.mock('@/stores/RepoDetailStore', () => ({
default: () => ({
initialize: vi.fn()
})
}));

vi.mock('@/stores/UserStore.js', () => ({
default: () => ({
username: 'test-namespace'
})
}));

vi.mock('@microsoft/fetch-event-source', () => ({
fetchEventSource: vi.fn()
}));

vi.mock('element-plus', () => ({
ElMessage: vi.fn()
}));

const createWrapper = (props = {}) => {
return mount(EndpointDetail, {
props: {
currentPath: '/test-path',
defaultTab: 'overview',
actionName: 'view',
tags: {},
namespace: 'test-namespace',
modelName: 'test-model',
endpointId: 123,
...props
}
});
};

describe("EndpointDetail", () => {
it("mounts correctly", async () => {
const wrapper = createWrapper();
await wrapper.vm.$nextTick();
expect(wrapper.exists()).toBe(true);
});

it("fetches model detail on mount", async () => {
const wrapper = createWrapper();
await wrapper.vm.$nextTick();
expect(wrapper.vm.modelInfo).toEqual({
namespace: 'test-namespace',
name: 'test-model'
});
});
});
85 changes: 85 additions & 0 deletions frontend/src/components/__tests__/endpoints/EndpointItem.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { mount,waitFor } from '@vue/test-utils'
import EndpointItem from '@/components/endpoints/EndpointItem.vue'
import AppStatus from '@/components/application_spaces/AppStatus.vue'
import AppPayMode from '@/components/application_spaces/AppPayMode.vue'
import { copyToClipboard } from '@/packs/clipboard'

vi.mock('@/packs/clipboard', () => ({
copyToClipboard: vi.fn()
}))

describe('EndpointItem.vue', () => {
let wrapper
const endpoint = {
model_id: 'mockModelId',
deploy_id: 'mockDeployId',
deploy_name: 'Mock Deploy Name',
pay_mode: 'mockPayMode',
status: 'Running',
hardware: 'mockHardware',
repo_tag: 'mockRepoTag',
provider: 'mockProvider',
resource_type: 'mockResourceType',
endpoint: 'mockEndpointUrl'
}

beforeEach(() => {
wrapper = mount(EndpointItem, {
props: {
endpoint,
namespace: 'mockNamespace'
},
global: {
components: {
AppStatus,
AppPayMode
}
}
})
})

it('renders the correct link', () => {
const link = wrapper.find('a')
expect(link.attributes('href')).toBe('/endpoints/mockModelId/mockDeployId')
})

it('renders the correct deploy name', () => {
const deployName = wrapper.find('h3')
expect(deployName.text()).toBe('Mock Deploy Name')
})

it('renders AppPayMode with correct payMode', () => {
const appPayMode = wrapper.findComponent(AppPayMode)
expect(appPayMode.exists()).toBe(true)
expect(appPayMode.props('payMode')).toBe('mockPayMode')
})

it('renders AppStatus with correct appStatus and spaceResource', () => {
const appStatus = wrapper.findComponent(AppStatus)
expect(appStatus.exists()).toBe(true)
expect(appStatus.props('appStatus')).toBe('Running')
expect(appStatus.props('spaceResource')).toBe('mockHardware')
})

it('renders the endpoint URL when status is Running', () => {
const endpointUrl = wrapper.find('div.rounded-b-xl div:first-child')
expect(endpointUrl.text()).toBe('mockEndpointUrl')
})

it('does not render the copy icon when status is not Running', async () => {
await wrapper.setProps({ endpoint: { ...endpoint, status: 'Stopped' } })
const copyIcon = wrapper.findComponent({ name: 'SvgIcon' })
expect(copyIcon.exists()).toBe(false)
})

it('renders the copy icon when status is Running', () => {
const copyIcon = wrapper.findComponent({ name: 'SvgIcon' })
expect(copyIcon.exists()).toBe(true)
})

it('calls copyToClipboard with the correct endpoint URL when copy icon is clicked', async () => {
const copyIcon = wrapper.findComponent({ name: 'SvgIcon' })
await copyIcon.trigger('click')
expect(copyToClipboard).toHaveBeenCalledWith('mockEndpointUrl')
})
})
88 changes: 88 additions & 0 deletions frontend/src/components/__tests__/endpoints/EndpointLogs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, it, expect, vi } from "vitest";
import { ref } from 'vue'
import { mount } from "@vue/test-utils";
import EndpointLogs from "@/components/endpoints/EndpointLogs.vue";

vi.mock('vue3-cookies', () => ({
useCookies: () => ({
cookies: {
get: vi.fn().mockReturnValue('mocked-token')
}
})
}));

vi.mock('@microsoft/fetch-event-source', () => ({
fetchEventSource: vi.fn()
}));

describe('EndpointLogs', () => {
const mockInstances = ref([{ name: 'Instance 1' }, { name: 'Instance 2' }])

it('should render instances in select', async () => {
const wrapper = mount(EndpointLogs, {
props: {
instances: mockInstances.value,
modelId: '123',
deployId: 1,
},
})

await wrapper.find('.el-select').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.exists()).toBe(true)
})

it('should call refreshInstanceLogs on change', async () => {
const refreshInstanceLogs = vi.fn()
const wrapper = mount(EndpointLogs, {
props: {
instances: mockInstances.value,
modelId: '123',
deployId: 1,
},
methods: {
refreshInstanceLogs: refreshInstanceLogs,
}
})

// Trigger the dropdown to open
await wrapper.find('.el-select').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.exists()).toBe(true)
})

it('should sync instance logs on mount', () => {
const syncInstanceLogs = vi.fn()
const wrapper = mount(EndpointLogs, {
props: {
instances: mockInstances.value,
modelId: '123',
deployId: 1,
},
methods: {
syncInstanceLogs: syncInstanceLogs,
}
})
expect(wrapper.exists()).toBe(true)
})

it('should sync instance logs on instance change', async () => {
const mockInstances = ref([{ name: 'Instance 1' }, { name: 'Instance 2' }]);
const wrapper = mount(EndpointLogs, {
props: {
instances: mockInstances.value,
modelId: '123',
deployId: 1,
},
});
const syncInstanceLogs = vi.spyOn(wrapper.vm, 'syncInstanceLogs');
mockInstances.value.push({ name: 'Instance 3' });
await wrapper.setProps({ instances: mockInstances.value });
wrapper.vm.isLogsSSEConnected = false;
await wrapper.vm.$nextTick();
if (wrapper.vm.isLogsSSEConnected) {
wrapper.vm.isLogsSSEConnected = false;
expect(syncInstanceLogs).toHaveBeenCalled();
}
});
})
92 changes: 92 additions & 0 deletions frontend/src/components/__tests__/endpoints/EndpointPage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { mount } from '@vue/test-utils'
import EndpointPage from '@/components/endpoints/EndpointPage.vue'
import EndpointPlayground from '@/components/endpoints/EndpointPlayground.vue'

vi.mock('@/packs/useFetchApi', () => ({
default: (url) => {
return {
json: () => {
if (url.includes('/space_resources')) {
return Promise.resolve({
data: {
value: {
data: [
{ name: 'Resource 1', is_available: true, resources: 'res1', id: 1 },
{ name: 'Resource 2', is_available: false, resources: 'res2', id: 2 }
]
}
},
error: { value: null }
})
}
},
post: () => ({
json: () => {
if (url.includes('finetune?current_user')) {
return Promise.resolve({
error: { value: null }, data: {
deploy_id: 123
}
})
}
return Promise.resolve({ error: { value: null } })
}
}),
put: () => ({
json: () => Promise.resolve({ error: { value: null } })
}),
delete: () => ({
json: () => Promise.resolve({ error: { value: null } })
}),
};
}
}));
vi.mock('element-plus', async () => {
const actual = await vi.importActual('element-plus')
return {
...actual,
ElMessage: vi.fn()
}
})

describe('EndpointPage.vue', () => {
let wrapper
const propsData = {
appEndpoint: 'http://mockEndpoint',
appStatus: 'Running',
clusterId: 'mockClusterId',
sku: 'mockSku',
modelId: 'mockModelId',
private: 'true',
replicaList: [
{ name: 'Replica1', status: 'Running' },
{ name: 'Replica2', status: 'Stopped' }
],
endpointReplica: 2
}

beforeEach(() => {
wrapper = mount(EndpointPage, {
props: propsData,
global: {
components: {
EndpointPlayground
}
}
})
})

it('renders EndpointPlayground when appStatus is Running and appEndpoint is provided', () => {
expect(wrapper.findComponent(EndpointPlayground).exists()).toBe(true)
})

it('does not render EndpointPlayground when appStatus is not Running', async () => {
await wrapper.setProps({ appStatus: 'Stopped' })
expect(wrapper.findComponent(EndpointPlayground).exists()).toBe(false)
})

it('does not render EndpointPlayground when appEndpoint is not provided', async () => {
await wrapper.setProps({ appEndpoint: '' })
expect(wrapper.findComponent(EndpointPlayground).exists()).toBe(false)
})
})
Loading

0 comments on commit 21950ae

Please sign in to comment.