Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • addRAGSources
  • chatting
  • conversations
  • editUser
  • fixTests
  • inputValidation
  • loadTests
  • main
  • rag
  • testing
  • updateEmbeddings
  • userauth
  • userhandling
13 results

Target

Select target project
  • zbhai/ragchat-api
1 result
Select Git revision
  • addRAGSources
  • chatting
  • conversations
  • editUser
  • fixTests
  • inputValidation
  • loadTests
  • main
  • rag
  • testing
  • updateEmbeddings
  • userauth
  • userhandling
13 results
Show changes

Commits on Source 12

Showing
with 2109 additions and 15 deletions
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai chat with model > given a chatId was sended > should respond with a proper body 1`] = `
{
"answer": "It seems you're asking when the pain of mocking (in the context of software development) subsides. Well, with practice and experience, you'll find that your code becomes more robust, and the process of writing unit tests using mocking libraries becomes more natural. The initial discomfort will give way to a sense of confidence and efficiency as you master the art of mocking.",
"chat": {
"__v": 0,
"chatHistory": [
{
"data": {
"additional_kwargs": {},
"content": "When does mocking stops feeling like torture?",
"response_metadata": {},
},
"type": "human",
},
{
"data": {
"additional_kwargs": {},
"content": "It seems you're asking when the pain of mocking (in the context of software development) subsides. Well, with practice and experience, you'll find that your code becomes more robust, and the process of writing unit tests using mocking libraries becomes more natural. The initial discomfort will give way to a sense of confidence and efficiency as you master the art of mocking.",
"invalid_tool_calls": [],
"response_metadata": {},
"source": "pretrained",
"tool_calls": [],
},
"type": "ai",
},
],
"createdAt": "2024-07 - 28T18: 39:02.388Z",
"createdBy": "66a29da2942b3ebcaf047f07",
"id": "66a69046251164018e7b1812",
"title": "When Sarcasm Becomes Sweet",
"updatedAt": "2024-07 - 28T18: 39:02.388Z",
},
}
`;
exports[`ai chat with model > given no chatId sended > should respond with a proper body 1`] = `
{
"answer": "It seems you're asking when the pain of mocking (in the context of software development) subsides. Well, with practice and experience, you'll find that your code becomes more robust, and the process of writing unit tests using mocking libraries becomes more natural. The initial discomfort will give way to a sense of confidence and efficiency as you master the art of mocking.",
"chat": {
"__v": 0,
"chatHistory": [
{
"data": {
"additional_kwargs": {},
"content": "When does mocking stops feeling like torture?",
"response_metadata": {},
},
"type": "human",
},
{
"data": {
"additional_kwargs": {},
"content": "It seems you're asking when the pain of mocking (in the context of software development) subsides. Well, with practice and experience, you'll find that your code becomes more robust, and the process of writing unit tests using mocking libraries becomes more natural. The initial discomfort will give way to a sense of confidence and efficiency as you master the art of mocking.",
"invalid_tool_calls": [],
"response_metadata": {},
"source": "pretrained",
"tool_calls": [],
},
"type": "ai",
},
],
"createdAt": "2024-07 - 28T18: 39:02.388Z",
"createdBy": "66a29da2942b3ebcaf047f07",
"id": "66a69046251164018e7b1812",
"title": "When Sarcasm Becomes Sweet",
"updatedAt": "2024-07 - 28T18: 39:02.388Z",
},
}
`;
exports[`ai chat with model > given no jwt sended > should respond with a proper body 1`] = `
{
"message": "No access token found. Access denied.",
}
`;
exports[`ai chat with model > given no matching model found > should respond with a proper body 1`] = `
{
"error": "Chat model unknownModel not found.",
}
`;
exports[`ai chat with model > given no valid jwt sended > should respond with a proper body 1`] = `
{
"message": "Access token is no longer valid. Access denied.",
}
`;
exports[`ai chat with model > given required fields are missing > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"model": "Required",
},
}
`;
exports[`ai chat with model > given valid chatId sended > should respond with a proper body 1`] = `
{
"message": "No chat history with ID 66a2af22df510b35b401f5ba found.",
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai get users chats > given no jwt sended > should respond with a proper body 1`] = `
{
"message": "No access token found. Access denied.",
}
`;
exports[`ai get users chats > given no valid jwt sended > should respond with a proper body 1`] = `
{
"message": "Access token is no longer valid. Access denied.",
}
`;
exports[`ai get users chats > given the inputs are valid > should respond with a proper body 1`] = `
{
"chats": {
"chats": [
{
"chatHistory": [
{
"data": {
"additional_kwargs": {},
"content": "Under what path could members of the working group can find the exam git directory?",
"response_metadata": {},
},
"type": "human",
},
{
"data": {
"additional_kwargs": {},
"content": "Members of the working group can find the exam git directory under the following path:
/home/samba/amd/AMD_Lehre/GCI_Grundlagen_Chemieinformatik",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": [],
},
"type": "ai",
},
{
"data": {
"additional_kwargs": {},
"content": "What else can be found under that path?",
"response_metadata": {},
},
"type": "human",
},
{
"data": {
"additional_kwargs": {},
"content": "According to the context, members of the working group can also find past semester results under the same path.",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": [],
},
"type": "ai",
},
],
"collectionId": "fkhmqgmmxx7svya",
"collectionName": "chats",
"created": "2024-07-15 06:17:31.815Z",
"id": "vwbprn1sxo7qx9k",
"title": ""Finding the Exam: A Path to Discovery"",
"updated": "2024-07-15 13:12:50.915Z",
"user": "jr9mt8yvuri3sbd",
},
{
"chatHistory": [
{
"data": {
"additional_kwargs": {},
"content": "What's the diameter of the planet earth?",
"response_metadata": {},
},
"type": "human",
},
{
"data": {
"additional_kwargs": {},
"content": "The diameter of the Earth is approximately 12,742 kilometers (7,918 miles).",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": [],
},
"type": "ai",
},
{
"data": {
"additional_kwargs": {},
"content": "and how far away is the moon?",
"response_metadata": {},
},
"type": "human",
},
{
"data": {
"additional_kwargs": {},
"content": "The average distance from the Earth to the Moon is approximately 384,400 kilometers (238,900 miles).",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": [],
},
"type": "ai",
},
],
"collectionId": "fkhmqgmmxx7svya",
"collectionName": "chats",
"created": "2024-07-08 11:46:22.267Z",
"id": "yc9vnbs4aj8iux2",
"title": "
"The Earth's Diameter: A Matter of Scale"",
"updated": "2024-07-08 11:47:21.830Z",
"user": "jr9mt8yvuri3sbd",
},
],
},
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai delete model > given a user tries to access > should respond with a proper body 1`] = `
{
"message": "Access Forbidden",
}
`;
exports[`ai delete model > given no jwt sended > should respond with a proper body 1`] = `
{
"message": "No access token found. Access denied.",
}
`;
exports[`ai delete model > given no matching model found > should respond with a proper body 1`] = `
{
"message": "pull model manifest: file does not exist",
}
`;
exports[`ai delete model > given no valid jwt sended > should respond with a proper body 1`] = `
{
"message": "Access token is no longer valid. Access denied.",
}
`;
exports[`ai delete model > given required fields are missing > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"model": "Required",
},
}
`;
exports[`ai delete model > given the inputs are valid > should respond with a proper body 1`] = `
{
"status": "success",
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai model > given no jwt sended > should respond with a proper body 1`] = `
{
"message": "No access token found. Access denied.",
}
`;
exports[`ai model > given no matching model found > should respond with a proper body 1`] = `
{
"message": "model 'xxx' not found",
}
`;
exports[`ai model > given no valid jwt sended > should respond with a proper body 1`] = `
{
"message": "Access token is no longer valid. Access denied.",
}
`;
exports[`ai model > given required fields are missing > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"model": "Required",
},
}
`;
exports[`ai model > given the inputs are valid > should respond with a proper body 1`] = `
{
"details": {
"families": [
"llama",
],
"family": "llama",
"format": "gguf",
"parameter_size": "8.0B",
"parent_model": "",
"quantization_level": "Q4_0",
},
"license": Any<String>,
"modelfile": Any<String>,
"parameters": Any<String>,
"template": Any<String>,
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai models > given no jwt sended > should respond with a proper body 1`] = `
{
"message": "No access token found. Access denied.",
}
`;
exports[`ai models > given no matching model found > should respond with a proper body 1`] = `[]`;
exports[`ai models > given no valid jwt sended > should respond with a proper body 1`] = `
{
"message": "Access token is no longer valid. Access denied.",
}
`;
exports[`ai models > given required fields are missing > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"filter": "Required",
},
}
`;
exports[`ai models > given the inputs are valid > should respond with a proper body 1`] = `
[
{
"details": {
"families": [
"llama",
],
"family": "llama",
"format": "gguf",
"parameter_size": "8.0B",
"parent_model": "",
"quantization_level": "Q4_0",
},
"digest": "365c0bd3c000a25d28ddbf732fe1c6add414de7275464c4e4d1c3b5fcb5d8ad1",
"model": "llama3:latest",
"modified_at": "2024-07-25T20:25:26.869024101+02:00",
"name": "llama3:latest",
"size": 4661224676,
},
{
"details": {
"families": [
"llama",
],
"family": "llama",
"format": "gguf",
"parameter_size": "13B",
"parent_model": "",
"quantization_level": "Q4_0",
},
"digest": "d475bf4c50bc4d29f333023e38cd56535039eec11052204e5304c8773cc8416c",
"model": "llama2:13b",
"modified_at": "2024-06-15T16:39:07.956494263+02:00",
"name": "llama2:13b",
"size": 7366821294,
},
{
"details": {
"families": [
"llama",
],
"family": "llama",
"format": "gguf",
"parameter_size": "7B",
"parent_model": "",
"quantization_level": "Q4_0",
},
"digest": "78e26419b4469263f75331927a00a0284ef6544c1975b826b15abdaef17bb962",
"model": "llama2:latest",
"modified_at": "2024-06-13T11:00:05.975159345+02:00",
"name": "llama2:latest",
"size": 3826793677,
},
]
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai pull model > given a user tries to access > should respond with a proper body 1`] = `
{
"message": "Access Forbidden",
}
`;
exports[`ai pull model > given no jwt sended > should respond with a proper body 1`] = `
{
"message": "No access token found. Access denied.",
}
`;
exports[`ai pull model > given no matching model found > should respond with a proper body 1`] = `
{
"message": "pull model manifest: file does not exist",
}
`;
exports[`ai pull model > given no valid jwt sended > should respond with a proper body 1`] = `
{
"message": "Access token is no longer valid. Access denied.",
}
`;
exports[`ai pull model > given required fields are missing > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"model": "Required",
},
}
`;
exports[`ai pull model > given the inputs are valid > should respond with a proper body 1`] = `
{
"status": "success",
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ai status > given ai is not running > should respond with a proper body 1`] = `
{
"running": false,
}
`;
exports[`ai status > given ai is running > should respond with a proper body 1`] = `
{
"running": true,
}
`;
// import vitest, supertest & app
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
import jwt from 'jsonwebtoken';
// set route
const ROUTE = '/ai/chat';
// prepare response of each test
let response;
// ############################
// OBJECTS
// ############################
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 4,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
},
foundModels: {
models: [
{
"name": "llama3:latest",
"model": "llama3:latest",
"modified_at": "2024-07-25T20:25:26.869024101+02:00",
"size": 4661224676,
"digest": "365c0bd3c000a25d28ddbf732fe1c6add414de7275464c4e4d1c3b5fcb5d8ad1",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "8.0B",
"quantization_level": "Q4_0"
}
},
{
"name": "mxbai-embed-large:latest",
"model": "mxbai-embed-large:latest",
"modified_at": "2024-05-17T20:38:00.241769083+02:00",
"size": 669615493,
"digest": "468836162de7f81e041c43663fedbbba921dcea9b9fefea135685a39b2d83dd8",
"details": {
"parent_model": "",
"format": "gguf",
"family": "bert",
"families": [
"bert"
],
"parameter_size": "334M",
"quantization_level": "F16"
}
}
]
},
emptyChatRecord: {
title: 'When Sarcasm Becomes Sweet',
id: '66a69046251164018e7b1812',
createdAt: "2024-07 - 28T18: 39:02.388Z",
updatedAt: "2024-07 - 28T18: 39:02.388Z",
createdBy: '66a29da2942b3ebcaf047f07',
__v: 0
},
filledChatRecord: {
title: 'When Sarcasm Becomes Sweet',
id: '66a69046251164018e7b1812',
createdAt: "2024-07 - 28T18: 39:02.388Z",
updatedAt: "2024-07 - 28T18: 39:02.388Z",
createdBy: '66a29da2942b3ebcaf047f07',
__v: 0,
createdBy: "66a29da2942b3ebcaf047f07",
chatHistory: [
{
type: "human",
data: {
content: "When does mocking stops feeling like torture?",
additional_kwargs: {},
response_metadata: {}
}
},
{
type: "ai",
data: {
content: "It seems you're asking when the pain of mocking (in the context of software development) subsides. Well, with practice and experience, you'll find that your code becomes more robust, and the process of writing unit tests using mocking libraries becomes more natural. The initial discomfort will give way to a sense of confidence and efficiency as you master the art of mocking.",
source: "pretrained",
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: {},
response_metadata: {}
}
}
]
},
answer: "It seems you're asking when the pain of mocking (in the context of software development) subsides. Well, with practice and experience, you'll find that your code becomes more robust, and the process of writing unit tests using mocking libraries becomes more natural. The initial discomfort will give way to a sense of confidence and efficiency as you master the art of mocking.",
validInput: {
model: 'llama3',
input: 'When does mocking stops feeling like torture?',
chatId: '66a2af22df510b35b401f5ba',
},
summaryContent: "Finding salvation: A path through mocking",
};
});
// ############################
// MOCKS
// ############################
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
createRecord: vi.fn(() => mockedVals.emptyChatRecord),
};
});
// import AI Service
import * as aiService from '../../utils/handleAI.js';
// mock aiService
vi.mock('../../utils/handleAI.js', async (importOriginal) => {
return {
...await importOriginal(),
aiGetModels: vi.fn(() => mockedVals.foundModels),
summarizeText: vi.fn(() => mockedVals.summaryContent),
chat: vi.fn().mockImplementation((req, res, next) => {
return res.json({ answer: mockedVals.answer, chat: mockedVals.filledChatRecord });
})
};
});
// ############################
// TESTS
// ############################
describe('ai chat with model', () => {
const _jwt = (id, role) => {
return jwt.sign({ id, role }, process.env.JWT_SECRET_KEY, { expiresIn: process.env.JWT_TTL });
};
describe('given a chatId was sended', () => {
beforeAll(async () => {
dbService.findOneRecord.mockImplementationOnce(() => mockedVals.filledChatRecord);
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given valid chatId sended', () => {
beforeAll(async () => {
dbService.findOneRecord.mockImplementationOnce(() => null);
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(404);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no chatId sended', () => {
beforeAll(async () => {
const { chatId, ...input } = mockedVals.validInput;
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no matching model found', () => {
beforeAll(async () => {
const input = { ...mockedVals.validInput, model: 'unknownModel' };
aiService.aiGetModels.mockImplementation(() => { return { models: [] }; });
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(500);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given required fields are missing', () => {
beforeAll(async () => {
const { model, ...input } = mockedVals.validInput;
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(400);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no valid jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer invalid`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
});
\ No newline at end of file
// import vitest, supertest & app
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
import jwt from 'jsonwebtoken';
// set route
const ROUTE = '/ai/chats';
// prepare response of each test
let response;
// ############################
// OBJECTS
// ############################
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 0,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
},
foundChats: {
"chats": [
{
"chatHistory": [
{
"data": {
"additional_kwargs": {},
"content": "Under what path could members of the working group can find the exam git directory?",
"response_metadata": {}
},
"type": "human"
},
{
"data": {
"additional_kwargs": {},
"content": "Members of the working group can find the exam git directory under the following path:\n\n/home/samba/amd/AMD_Lehre/GCI_Grundlagen_Chemieinformatik",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": []
},
"type": "ai"
},
{
"data": {
"additional_kwargs": {},
"content": "What else can be found under that path?",
"response_metadata": {}
},
"type": "human"
},
{
"data": {
"additional_kwargs": {},
"content": "According to the context, members of the working group can also find past semester results under the same path.",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": []
},
"type": "ai"
}
],
"collectionId": "fkhmqgmmxx7svya",
"collectionName": "chats",
"created": "2024-07-15 06:17:31.815Z",
"id": "vwbprn1sxo7qx9k",
"title": "\"Finding the Exam: A Path to Discovery\"",
"updated": "2024-07-15 13:12:50.915Z",
"user": "jr9mt8yvuri3sbd"
},
{
"chatHistory": [
{
"data": {
"additional_kwargs": {},
"content": "What's the diameter of the planet earth?",
"response_metadata": {}
},
"type": "human"
},
{
"data": {
"additional_kwargs": {},
"content": "The diameter of the Earth is approximately 12,742 kilometers (7,918 miles).",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": []
},
"type": "ai"
},
{
"data": {
"additional_kwargs": {},
"content": "and how far away is the moon?",
"response_metadata": {}
},
"type": "human"
},
{
"data": {
"additional_kwargs": {},
"content": "The average distance from the Earth to the Moon is approximately 384,400 kilometers (238,900 miles).",
"invalid_tool_calls": [],
"response_metadata": {},
"tool_calls": []
},
"type": "ai"
}
],
"collectionId": "fkhmqgmmxx7svya",
"collectionName": "chats",
"created": "2024-07-08 11:46:22.267Z",
"id": "yc9vnbs4aj8iux2",
"title": "\n\"The Earth's Diameter: A Matter of Scale\"",
"updated": "2024-07-08 11:47:21.830Z",
"user": "jr9mt8yvuri3sbd"
}
]
}
};
});
// ############################
// MOCKS
// ############################
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
findRecords: vi.fn(() => mockedVals.foundChats),
};
});
// ############################
// TESTS
// ############################
describe('ai get users chats', () => {
const _jwt = (id, role) => {
return jwt.sign({ id, role }, process.env.JWT_SECRET_KEY, { expiresIn: process.env.JWT_TTL });
};
describe('given the inputs are valid', () => {
beforeAll(async () => {
response = await supertest(app)
.get(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send();
});
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no valid jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.get(ROUTE)
.set('Authorization', `Bearer invalid`)
.send();
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.get(ROUTE)
.send();
});
it('should return a proper status code status', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
});
\ No newline at end of file
......@@ -2,10 +2,8 @@
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
// ignore expiration of the (self-signed) certificate
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
// set timeout
const BEFORE_ALL_TIMEOUT = 30000; // 30 sec
import jwt from 'jsonwebtoken';
// set route
const ROUTE = '/ai/models';
// prepare response of each test
......@@ -14,27 +12,37 @@ let response;
// ############################
// OBJECTS
// ############################
const noModelFoundResponse = {
code: 500,
message: "no such file or directory",
data: {}
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 4,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
},
validInput: {
model: 'llama3'
}
};
});
// ############################
// MOCKS
// ############################
// import PocketBase Service
import * as pbService from '../../utils/pocketbase/handlePocketBase.js';
// mock pbService
vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => {
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
pbVerifyAccessToken: vi.fn().mockImplementation((req, res, next) => {
next();
}),
gateKeeper: vi.fn().mockImplementation((req, res, next) => {
return next();
})
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
};
});
......@@ -53,103 +61,127 @@ vi.mock('../../utils/handleAI.js', async (importOriginal) => {
// ############################
describe('ai delete model', () => {
const _jwt = (id, role) => {
return jwt.sign({ id, role }, process.env.JWT_SECRET_KEY, { expiresIn: process.env.JWT_TTL });
};
describe('given the inputs are valid', async () => {
beforeAll(async () => {
response = await supertest(app)
.delete(ROUTE)
.set('Authorization', 'Bearer 123valid')
.send({ model: 'validModelName' });
it('should have called the gateKeeper', () => {
expect(pbService.gateKeeper).toHaveBeenCalledTimes(1);
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(mockedVals.validInput);
});
it('should return a proper status code', () => {
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper message', () => {
expect(response.body).toEqual({ status: 'success' });
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no valid JWT sent', () => {
describe('given no matching model found', () => {
beforeAll(async () => {
pbService.pbVerifyAccessToken.mockImplementationOnce((req, res, next) => {
return res.status(403).json({ message: 'You are not logged in.' });
});
const input = { ...mockedVals.validInput, model: 'unknownModel' };
let error = new Error("pull model manifest: file does not exist");
error.name = 'ResponseError';
error.status = 500;
aiService.aiDeleteModel.mockImplementation(() => { throw error; });
response = await supertest(app)
.delete(ROUTE)
.send({ model: 'validModelName' });
}, BEFORE_ALL_TIMEOUT);
it('should return a proper status code', () => {
expect(response.status).toBe(403);
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should respond with a proper message', () => {
expect(response.body.message).toEqual('You are not logged in.');
it('should return a proper status code status', () => {
expect(response.status).toBe(500);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no access granted', () => {
describe('given required fields are missing', () => {
beforeAll(async () => {
pbService.gateKeeper.mockImplementationOnce((req, res, next) => {
return res.status(403).json({ message: 'Access Forbidden' });
const { model, ...input } = mockedVals.validInput;
response = await supertest(app)
.delete(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(400);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given a user tries to access', () => {
beforeAll(async () => {
dbService.findOneRecord.mockImplementationOnce(async () => {
return { ...mockedVals.foundUser, role: 0 };
});
response = await supertest(app)
.delete(ROUTE)
.set('Authorization', 'Bearer 123valid');
}, BEFORE_ALL_TIMEOUT);
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, 0)}`)
.send(mockedVals.validInput);
});
it('should return a proper status code', () => {
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper message', () => {
expect(response.body.message).toEqual('Access Forbidden');
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no model name sent', () => {
describe('given no valid jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.delete(ROUTE)
.set('Authorization', 'Bearer 123valid');
}, BEFORE_ALL_TIMEOUT);
it('should return a proper status code', () => {
expect(response.status).toBe(400);
.set('Authorization', `Bearer invalid`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper message', () => {
expect(response.body.validationErrors.model).toEqual('Required');
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no matching model found', () => {
describe('given no jwt sended', () => {
beforeAll(async () => {
let error = new Error('no such file or directory');
error.name = 'ResponseError';
error.response = noModelFoundResponse;
error.status = 500;
aiService.aiDeleteModel.mockImplementation(() => { throw error; });
response = await supertest(app)
.delete(ROUTE)
.set('Authorization', 'Bearer 123valid')
.send({ model: 'invalidModelName' });
}, BEFORE_ALL_TIMEOUT);
it('should force aiDeleteModel to throw an error', () => {
expect(aiService.aiDeleteModel).toThrowError();
.send(mockedVals.validInput);
});
it('should return a proper status code', () => {
expect(response.status).toBe(500);
it('should return a proper status code status', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper message', () => {
expect(response.body.message).toEqual('no such file or directory');
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
......
......@@ -2,10 +2,8 @@
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
// ignore expiration of the (self-signed) certificate
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
// set timeout
const BEFORE_ALL_TIMEOUT = 30000; // 30 sec
import jwt from 'jsonwebtoken';
// set route
const ROUTE = '/ai/model';
// prepare response of each test
......@@ -14,51 +12,64 @@ let response;
// ############################
// OBJECTS
// ############################
const modelFoundResponse = {
"license": "META LLAMA 3 COMMUNITY LICENSE AGREEMENT WALL OF TEXT",
"modelfile": "# Modelfile generated by \"ollama show\"\n# ANOTHER WALL OF TEXT",
"parameters": "num_keep 24\nstop \"<|start_header_id|>\"\nstop \"<|end_header_id|>\"\nstop \"<|eot_id|>\"",
"template": "{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|>",
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 0,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
},
foundModel: {
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
"llama",
],
"family": "llama",
"format": "gguf",
"parameter_size": "8.0B",
"quantization_level": "Q4_0"
"parent_model": "",
"quantization_level": "Q4_0",
},
"license": "someTexts",
"modelfile": "someTexts",
"parameters": "someTexts",
"template": "someTexts"
},
validInput: {
model: 'llama3'
}
};
});
const noModelFoundResponse = {
code: 400,
message: "model 'a' not found",
data: {}
};
// ############################
// MOCKS
// ############################
// import PocketBase Service
import * as pbService from '../../utils/pocketbase/handlePocketBase.js';
// mock pbService
vi.mock('../../utils/pocketbase/handlePocketBase.js', async (importOriginal) => {
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
pbVerifyAccessToken: vi.fn().mockImplementation((req, res, next) => {
next();
})
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
};
});
// import AI Service
import * as aiService from '../../utils/handleAI.js';
// const spyIsRunning = vi.spyOn(aiService, 'isRunning');
// mock aiService
import * as aiService from '../../utils/handleAI.js';
vi.mock('../../utils/handleAI.js', async (importOriginal) => {
return {
...await importOriginal(),
aiGetModel: vi.fn(() => modelFoundResponse)
aiGetModel: vi.fn(() => mockedVals.foundModel)
};
});
......@@ -67,81 +78,107 @@ vi.mock('../../utils/handleAI.js', async (importOriginal) => {
// ############################
// "message": "model 'a' not found"
describe('ai model', () => {
const _jwt = () => {
return jwt.sign({ id: mockedVals.foundUser.id, role: mockedVals.foundUser.role }, process.env.JWT_SECRET_KEY, { expiresIn: process.env.JWT_TTL });
};
describe('given the inputs are valid', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.set('Authorization', 'Bearer 123valid')
.send({ model: 'validModelName' });
}, BEFORE_ALL_TIMEOUT);
it('should call required mocks', () => {
expect(aiService.aiGetModel()).toEqual(modelFoundResponse);
.set('Authorization', `Bearer ${_jwt()}`)
.send(mockedVals.validInput);
});
it('should return a proper status code', () => {
expect(response.status).toBe(200);
});
it('should respond with matching models', () => {
expect(response.body).toEqual(modelFoundResponse);
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot({
license: expect.any(String),
modelfile: expect.any(String),
parameters: expect.any(String),
template: expect.any(String),
});
});
});
// ############################
describe('given no valid JWT sent', () => {
describe('given no matching model found', () => {
beforeAll(async () => {
pbService.pbVerifyAccessToken.mockImplementationOnce((req, res, next) => {
return res.status(403).json({ message: 'You are not logged in.' });
});
let error = new Error("model 'xxx' not found");
error.name = 'ResponseError';
error.status = 400;
aiService.aiGetModel.mockImplementation(() => { throw error; });
response = await supertest(app)
.post(ROUTE)
.send({ model: 'validModelName' });
}, BEFORE_ALL_TIMEOUT);
.set('Authorization', `Bearer ${_jwt()}`)
.send({ model: 'xxx' });
});
it('should return a proper status code', () => {
expect(response.status).toBe(403);
expect(response.status).toBe(500);
});
it('should respond with a proper message', () => {
expect(response.body.message).toEqual('You are not logged in.');
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no model name sent', () => {
describe('given required fields are missing', () => {
beforeAll(async () => {
const { model, ...input } = mockedVals.validInput;
response = await supertest(app)
.post(ROUTE)
.set('Authorization', 'Bearer 123valid');
}, BEFORE_ALL_TIMEOUT);
it('should return a proper status code', () => {
.set('Authorization', `Bearer ${_jwt()}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(400);
});
it('should respond with a proper message', () => {
expect(response.body.validationErrors.model).toEqual('Required');
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
describe('given no matching model found', () => {
// ############################
describe('given no valid jwt sended', () => {
beforeAll(async () => {
let error = new Error("model 'a' not found");
error.name = 'ResponseError';
error.response = noModelFoundResponse;
error.status = 400;
aiService.aiGetModel.mockImplementation(() => { throw error; });
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer invalid`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.set('Authorization', 'Bearer 123valid')
.send({ model: 'a' });
}, BEFORE_ALL_TIMEOUT);
it('should return a proper status code', () => {
expect(response.status).toBe(500);
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper message', () => {
expect(response.body.message).toEqual("model 'a' not found");
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
......
// import vitest, supertest & app
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
import jwt from 'jsonwebtoken';
// set route
const ROUTE = '/ai/models';
// prepare response of each test
let response;
// ############################
// OBJECTS
// ############################
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 0,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
},
foundModels: {
models: [
{
"name": "llama3:latest",
"model": "llama3:latest",
"modified_at": "2024-07-25T20:25:26.869024101+02:00",
"size": 4661224676,
"digest": "365c0bd3c000a25d28ddbf732fe1c6add414de7275464c4e4d1c3b5fcb5d8ad1",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "8.0B",
"quantization_level": "Q4_0"
}
},
{
"name": "orca2:13b",
"model": "orca2:13b",
"modified_at": "2024-06-15T16:53:37.368220025+02:00",
"size": 7365868139,
"digest": "a8dcfac3ac32d06f6241896d56928ac7b1d7a6e7f5dcc6b2aec69f2194a9f091",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": null,
"parameter_size": "13B",
"quantization_level": "Q4_0"
}
},
{
"name": "llama2:13b",
"model": "llama2:13b",
"modified_at": "2024-06-15T16:39:07.956494263+02:00",
"size": 7366821294,
"digest": "d475bf4c50bc4d29f333023e38cd56535039eec11052204e5304c8773cc8416c",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "13B",
"quantization_level": "Q4_0"
}
},
{
"name": "starling-lm:latest",
"model": "starling-lm:latest",
"modified_at": "2024-06-15T16:12:29.439449821+02:00",
"size": 4108940286,
"digest": "39153f619be614bf1b8b91cf31afe53ec107d70b6b7bb4118aa52bccc107ca7e",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "7B",
"quantization_level": "Q4_0"
}
},
{
"name": "llama2:latest",
"model": "llama2:latest",
"modified_at": "2024-06-13T11:00:05.975159345+02:00",
"size": 3826793677,
"digest": "78e26419b4469263f75331927a00a0284ef6544c1975b826b15abdaef17bb962",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "7B",
"quantization_level": "Q4_0"
}
},
{
"name": "mistral:latest",
"model": "mistral:latest",
"modified_at": "2024-06-13T11:00:05.455160458+02:00",
"size": 4113301090,
"digest": "2ae6f6dd7a3dd734790bbbf58b8909a606e0e7e97e94b7604e0aa7ae4490e6d8",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "7.2B",
"quantization_level": "Q4_0"
}
},
{
"name": "mxbai-embed-large:latest",
"model": "mxbai-embed-large:latest",
"modified_at": "2024-05-17T20:38:00.241769083+02:00",
"size": 669615493,
"digest": "468836162de7f81e041c43663fedbbba921dcea9b9fefea135685a39b2d83dd8",
"details": {
"parent_model": "",
"format": "gguf",
"family": "bert",
"families": [
"bert"
],
"parameter_size": "334M",
"quantization_level": "F16"
}
}
]
},
validInput: {
filter: 'llama'
}
};
});
// ############################
// MOCKS
// ############################
// import Database Service
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
};
});
// mock aiService
import * as aiService from '../../utils/handleAI.js';
vi.mock('../../utils/handleAI.js', async (importOriginal) => {
return {
...await importOriginal(),
aiGetModels: vi.fn(() => mockedVals.foundModels)
};
});
// ############################
// TESTS
// ############################
describe('ai models', () => {
const _jwt = () => {
return jwt.sign({ id: mockedVals.foundUser.id, role: mockedVals.foundUser.role }, process.env.JWT_SECRET_KEY, { expiresIn: process.env.JWT_TTL });
};
describe('given the inputs are valid', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt()}`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no matching model found', () => {
beforeAll(async () => {
const input = { ...mockedVals.validInput, model: 'unknownModel' };
aiService.aiGetModels.mockImplementation(() => { return { models: [] }; });
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given required fields are missing', () => {
beforeAll(async () => {
const { filter, ...input } = mockedVals.validInput;
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer ${_jwt()}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(400);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no valid jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.set('Authorization', `Bearer invalid`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.post(ROUTE)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
});
\ No newline at end of file
// import vitest, supertest & app
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
import jwt from 'jsonwebtoken';
// set route
const ROUTE = '/ai/models';
// prepare response of each test
let response;
// ############################
// OBJECTS
// ############################
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 4,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
},
foundModels: {
models: [
{
"name": "llama3:latest",
"model": "llama3:latest",
"modified_at": "2024-07-25T20:25:26.869024101+02:00",
"size": 4661224676,
"digest": "365c0bd3c000a25d28ddbf732fe1c6add414de7275464c4e4d1c3b5fcb5d8ad1",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "8.0B",
"quantization_level": "Q4_0"
}
},
{
"name": "orca2:13b",
"model": "orca2:13b",
"modified_at": "2024-06-15T16:53:37.368220025+02:00",
"size": 7365868139,
"digest": "a8dcfac3ac32d06f6241896d56928ac7b1d7a6e7f5dcc6b2aec69f2194a9f091",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": null,
"parameter_size": "13B",
"quantization_level": "Q4_0"
}
},
{
"name": "llama2:13b",
"model": "llama2:13b",
"modified_at": "2024-06-15T16:39:07.956494263+02:00",
"size": 7366821294,
"digest": "d475bf4c50bc4d29f333023e38cd56535039eec11052204e5304c8773cc8416c",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "13B",
"quantization_level": "Q4_0"
}
},
{
"name": "starling-lm:latest",
"model": "starling-lm:latest",
"modified_at": "2024-06-15T16:12:29.439449821+02:00",
"size": 4108940286,
"digest": "39153f619be614bf1b8b91cf31afe53ec107d70b6b7bb4118aa52bccc107ca7e",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "7B",
"quantization_level": "Q4_0"
}
},
{
"name": "llama2:latest",
"model": "llama2:latest",
"modified_at": "2024-06-13T11:00:05.975159345+02:00",
"size": 3826793677,
"digest": "78e26419b4469263f75331927a00a0284ef6544c1975b826b15abdaef17bb962",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "7B",
"quantization_level": "Q4_0"
}
},
{
"name": "mistral:latest",
"model": "mistral:latest",
"modified_at": "2024-06-13T11:00:05.455160458+02:00",
"size": 4113301090,
"digest": "2ae6f6dd7a3dd734790bbbf58b8909a606e0e7e97e94b7604e0aa7ae4490e6d8",
"details": {
"parent_model": "",
"format": "gguf",
"family": "llama",
"families": [
"llama"
],
"parameter_size": "7.2B",
"quantization_level": "Q4_0"
}
},
{
"name": "mxbai-embed-large:latest",
"model": "mxbai-embed-large:latest",
"modified_at": "2024-05-17T20:38:00.241769083+02:00",
"size": 669615493,
"digest": "468836162de7f81e041c43663fedbbba921dcea9b9fefea135685a39b2d83dd8",
"details": {
"parent_model": "",
"format": "gguf",
"family": "bert",
"families": [
"bert"
],
"parameter_size": "334M",
"quantization_level": "F16"
}
}
]
},
validInput: {
model: 'llama3'
}
};
});
// ############################
// MOCKS
// ############################
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
};
});
// import AI Service
import * as aiService from '../../utils/handleAI.js';
// mock aiService
vi.mock('../../utils/handleAI.js', async (importOriginal) => {
return {
...await importOriginal(),
aiInstallModel: vi.fn().mockImplementation(() => { return { status: 'success' }; }),
};
});
// ############################
// TESTS
// ############################
describe('ai pull model', () => {
const _jwt = (id, role) => {
return jwt.sign({ id, role }, process.env.JWT_SECRET_KEY, { expiresIn: process.env.JWT_TTL });
};
describe('given the inputs are valid', async () => {
beforeAll(async () => {
response = await supertest(app)
.put(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no matching model found', () => {
beforeAll(async () => {
const input = { ...mockedVals.validInput, model: 'unknownModel' };
let error = new Error("pull model manifest: file does not exist");
error.name = 'ResponseError';
error.status = 500;
aiService.aiInstallModel.mockImplementation(() => { throw error; });
response = await supertest(app)
.put(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(500);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given required fields are missing', () => {
beforeAll(async () => {
const { model, ...input } = mockedVals.validInput;
response = await supertest(app)
.put(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, mockedVals.foundUser.role)}`)
.send(input);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(400);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given a user tries to access', () => {
beforeAll(async () => {
dbService.findOneRecord.mockImplementationOnce(async () => {
return { ...mockedVals.foundUser, role: 0 };
});
response = await supertest(app)
.put(ROUTE)
.set('Authorization', `Bearer ${_jwt(mockedVals.foundUser.id, 0)}`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no valid jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.put(ROUTE)
.set('Authorization', `Bearer invalid`)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(403);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
// ############################
describe('given no jwt sended', () => {
beforeAll(async () => {
response = await supertest(app)
.put(ROUTE)
.send(mockedVals.validInput);
});
it('should return a proper status code status', () => {
expect(response.status).toBe(401);
});
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
});
\ No newline at end of file
......@@ -2,10 +2,7 @@
import { vi, beforeAll, beforeEach, describe, expect, expectTypeOf, test, it, afterEach } from 'vitest';
import supertest from "supertest";
import app from "../../app.js";
// ignore expiration of the (self-signed) certificate
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
// set timeout
const BEFORE_ALL_TIMEOUT = 30000; // 30 sec
// set route
const ROUTE = '/ai/status';
// prepare response of each test
......@@ -14,10 +11,37 @@ let response;
// ############################
// OBJECTS
// ############################
const mockedVals = vi.hoisted(() => {
return {
foundUser: {
_id: '66a29da2942b3eb',
username: 'snoopy',
name: 'My User',
email: 'user@mail.local',
verified: true,
role: 0,
createdAt: '2024-07 - 25T18: 46: 58.982Z',
updatedAt: '2024-07 - 25T18: 46: 58.982Z',
__v: 0,
fullname: '',
id: '66a29da2942b3ebcaf047f07'
}
};
});
// ############################
// MOCKS
// ############################
// import Database Service
import * as dbService from '../../utils/handleDB.js';
// mock dbService
vi.mock('../../utils/handleDB.js', async (importOriginal) => {
return {
...await importOriginal(),
dbConnection: vi.fn(() => 'mocked'),
findOneRecord: vi.fn(() => mockedVals.foundUser),
};
});
// import AI Service
import * as aiService from '../../utils/handleAI.js';
// mock aiService
......@@ -37,15 +61,12 @@ describe('ai status', () => {
beforeAll(async () => {
response = await supertest(app)
.get(ROUTE);
}, BEFORE_ALL_TIMEOUT);
it('should call required mocks', () => {
expect(aiService.aiIsRunning()).toEqual(true);
});
it('should return a proper status code', () => {
expect(response.status).toBe(200);
});
it('should respond with a proper record and token', () => {
expect(response.body).toEqual({ running: true });
}); it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
......@@ -56,15 +77,12 @@ describe('ai status', () => {
aiService.aiIsRunning.mockImplementation(() => false);
response = await supertest(app)
.get(ROUTE);
}, BEFORE_ALL_TIMEOUT);
it('should call required mocks', () => {
expect(aiService.aiIsRunning()).toEqual(false);
});
it('should return a proper status code', () => {
expect(response.status).toBe(404);
});
it('should respond with a proper record and token', () => {
expect(response.body).toEqual({ running: false });
it('should respond with a proper body', () => {
expect(response.body).toMatchSnapshot();
});
});
});
\ No newline at end of file
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`user confirm password reset > given refresh token is malformed > should respond with a proper body 1`] = `
{
"message": "Token is no longer valid.",
}
`;
exports[`user confirm password reset > given required fields are missing > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"confirmPassword": "Required",
},
}
`;
exports[`user confirm password reset > given the inputs are correct > should respond with a proper body 1`] = `
{
"message": "Password successfully reset. You can now login.",
}
`;
exports[`user confirm password reset > given the password and confirmPassword do not match > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"confirmPassword": "Passwords don't match",
},
}
`;
exports[`user confirm password reset > given token is invalid > should respond with a proper body 1`] = `
{
"message": "Token is no longer valid.",
}
`;
exports[`user confirm password reset > the request body is empty > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"confirmPassword": "Required",
"password": "Required",
"token": "Required",
},
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`user login > given email and password are valid, but accout is unverified > should respond with a proper body 1`] = `
{
"message": "Your account is still unverified. Check your emails for the verification link.",
}
`;
exports[`user login > given the email is unknown > should respond with a proper body 1`] = `
{
"message": "Unknown combination of login credentials.",
}
`;
exports[`user login > given the inputs are valid > should respond with a proper body 1`] = `
{
"accessToken": Any<String>,
"document": {
"__v": 0,
"_id": "66a29da2942b3eb",
"createdAt": "2024-07 - 25T18: 46: 58.982Z",
"email": "user@mail.local",
"id": "66a29da2942b3ebcaf047f07",
"name": "My User",
"role": 0,
"updatedAt": "2024-07 - 25T18: 46: 58.982Z",
"username": "snoopy",
"verified": true,
},
"message": "Successfully logged in",
}
`;
exports[`user login > given the password is wrong > should respond with a proper body 1`] = `
{
"message": "Unknown combination of login credentials",
}
`;
exports[`user login > given the request body is empty > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"email": "Required",
"password": "Required",
},
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`user logout > given no cookie was send > should respond with a proper body 1`] = `
{
"message": "See you soon.",
}
`;
exports[`user logout > given refresh token is invalid > should respond with a proper body 1`] = `
{
"message": "jwt malformed",
}
`;
exports[`user logout > given refresh token is valid > should respond with a proper body 1`] = `
{
"message": "jwt malformed",
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`refresh JWT > given authtoken is valid > should respond with a proper body 1`] = `
{
"accessToken": Any<String>,
"message": "Access token refreshed",
}
`;
exports[`refresh JWT > given refresh token is invalid > should respond with a proper body 1`] = `
{
"message": "Refresh token is invalid",
}
`;
exports[`refresh JWT > given refresh token is malformed > should respond with a proper body 1`] = `
{
"message": "jwt malformed",
}
`;
exports[`refresh JWT > given refresh token is missing > should respond with a proper body 1`] = `
{
"message": "Refresh token is missing. Consider to re-login",
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`user request password reset > given the email format is invalid > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"email": "Invalid email",
},
}
`;
exports[`user request password reset > given the email is unknown > should respond with a proper body 1`] = `
{
"message": "If the email **user@mail.local** is correct you will receive an eMail with further instructions.",
}
`;
exports[`user request password reset > given the inputs are valid > should respond with a proper body 1`] = `
{
"message": "If the email **user@mail.local** is correct you will receive an eMail with further instructions.",
}
`;
exports[`user request password reset > the request body is empty > should respond with a proper body 1`] = `
{
"message": "Validation errors. Please check the error messages.",
"validationErrors": {
"email": "Required",
},
}
`;