Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): enhance config loader with embedding support and impro… #74

Merged
merged 20 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
78a3545
feat(backend): enhance config loader with embedding support and impro…
Sma1lboy Dec 16, 2024
72c6dbc
feat(backend): implement ModelStatusManager for tracking model downlo…
Sma1lboy Dec 16, 2024
1340532
chore: adding uitls ufc
Sma1lboy Dec 16, 2024
0ee6b84
Merge branch 'main' of https://github.com/Sma1lboy/codefox into feat-…
NarwhalChen Dec 25, 2024
0b412b0
feat: adding embedding provider and openai-embedding provider
NarwhalChen Dec 25, 2024
b378185
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 25, 2024
4c66d05
feat: implmenting dynamic loader for embedding and models in hugging …
NarwhalChen Dec 28, 2024
ad74c49
Merge branch 'feat-adding-embedding-config' of https://github.com/Sma…
NarwhalChen Dec 28, 2024
97cae53
Merge branch 'main' into feat-adding-embedding-config
Sma1lboy Dec 28, 2024
e579146
feat(backend): refactor model downloading and configuration handling
Sma1lboy Dec 28, 2024
8b25760
Merge branch 'feat-adding-embedding-config' of https://github.com/Sma…
NarwhalChen Dec 30, 2024
b7a8b30
to: using fastemb to implement embedding downloader
NarwhalChen Dec 30, 2024
7e68b3a
to: moving embedding provider to backend
NarwhalChen Dec 30, 2024
2c2027d
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 30, 2024
fb9be7d
feat: request llm-server with openai/some embedding provider
NarwhalChen Dec 30, 2024
0b85081
Merge branch 'feat-adding-embedding-config' of https://github.com/Sma…
NarwhalChen Dec 30, 2024
6ad8b9b
to: adding return type and comments for generateEmbResponse
NarwhalChen Dec 31, 2024
56c6ffc
fix: fixing the change of env variable
NarwhalChen Jan 6, 2025
b9c1909
fix: deleting useless embedding index.ts
NarwhalChen Jan 6, 2025
50d3bd1
Merge branch 'main' into feat-adding-embedding-config
ZHallen122 Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions backend/src/config/config-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import * as fs from 'fs';
import * as path from 'path';
import { ConfigLoader, exampleConfigContent } from './config-loader';

jest.mock('fs');
jest.mock('path');

describe('ConfigLoader', () => {
const mockConfigPath = '/mock/path/config.json';

beforeEach(() => {
jest.clearAllMocks();
(fs.existsSync as jest.Mock).mockReset();
(fs.readFileSync as jest.Mock).mockReset();
(fs.writeFileSync as jest.Mock).mockReset();
(fs.mkdirSync as jest.Mock).mockReset();
});

describe('initConfigFile', () => {
it('should create a new config file with example configuration', () => {
(fs.existsSync as jest.Mock).mockReturnValue(false);

ConfigLoader.initConfigFile(mockConfigPath);

expect(fs.writeFileSync).toHaveBeenCalledWith(
mockConfigPath,
exampleConfigContent,
'utf-8',
);
});

it('should throw error if config file already exists', () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);

expect(() => {
ConfigLoader.initConfigFile(mockConfigPath);
}).toThrow('Config file already exists');
});

it('should handle JSON with comments when loading config', () => {
const configWithComments = `{
// This is a comment
"chats": {
"test": {
"model": "test-model" // Another comment
}
}
}`;

(fs.readFileSync as jest.Mock).mockReturnValue(configWithComments);
const loader = new ConfigLoader(mockConfigPath);

expect(loader.getChatConfig('test')).toEqual({ model: 'test-model' });
});
});

describe('constructor and loadConfig', () => {
it('should load existing config file', () => {
const mockConfig = { chats: { test: { model: 'test-model' } } };
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig('test')).toEqual({ model: 'test-model' });
});

it('should create empty config if file does not exist', () => {
(fs.readFileSync as jest.Mock).mockImplementation(() => {
throw { code: 'ENOENT' };
});

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig()).toBeNull();
});

it('should create empty config if file is empty', () => {
(fs.readFileSync as jest.Mock).mockImplementation(() => {
throw new Error('Unexpected end of JSON input');
});

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig()).toBeNull();
});
});

describe('getChatConfig', () => {
it('should return specific chat config when id is provided', () => {
const mockConfig = {
chats: {
test: { model: 'test-model' },
},
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig('test')).toEqual({ model: 'test-model' });
});

it('should return default chat config when no id is provided', () => {
const mockConfig = {
chats: {
default: { model: 'default-model', default: true },
other: { model: 'other-model' },
},
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig()).toEqual({
model: 'default-model',
default: true,
});
});

it('should return first available config when no default is set', () => {
const mockConfig = {
chats: {
first: { model: 'first-model' },
second: { model: 'second-model' },
},
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig()).toEqual({ model: 'first-model' });
});

it('should return null when chats config does not exist', () => {
const mockConfig = {};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getChatConfig()).toBeNull();
});
});

describe('getEmbeddingConfig', () => {
it('should return embedding config when it exists', () => {
const mockConfig = {
embeddings: { model: 'embedding-model' },
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getEmbeddingConfig()).toEqual({ model: 'embedding-model' });
});

it('should return null when embedding config does not exist', () => {
const mockConfig = {};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.getEmbeddingConfig()).toBeNull();
});
});

describe('validateConfig', () => {
it('should throw error when chat model is missing', () => {
const mockConfig = {
chats: {
test: { endpoint: 'test-endpoint' },
},
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

expect(() => {
new ConfigLoader(mockConfigPath);
}).toThrow("Invalid chat configuration for 'test': 'model' is required");
});

it('should throw error when embedding model is missing', () => {
const mockConfig = {
embeddings: { endpoint: 'test-endpoint' },
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

expect(() => {
new ConfigLoader(mockConfigPath);
}).toThrow("Invalid embedding configuration: 'model' is required");
});
});

describe('set and get', () => {
it('should set and save new configuration values', () => {
const mockConfig = {};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
loader.set('chats.test', { model: 'test-model' });

expect(fs.writeFileSync).toHaveBeenCalled();
expect(loader.getChatConfig('test')).toEqual({ model: 'test-model' });
});

it('should get configuration values using path', () => {
const mockConfig = {
chats: {
test: { model: 'test-model' },
},
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.get('chats.test.model')).toBe('test-model');
});

it('should return full config when no path is provided', () => {
const mockConfig = {
chats: {
test: { model: 'test-model' },
},
};
(fs.readFileSync as jest.Mock).mockReturnValue(
JSON.stringify(mockConfig),
);

const loader = new ConfigLoader(mockConfigPath);
expect(loader.get()).toEqual(mockConfig);
});
});
});
Loading
Loading