2024-01-07 01:53:09 +00:00
|
|
|
import { DatabaseExtension, DatabaseService, IDatabaseRepository, Version } from '@app/domain';
|
2023-12-21 11:06:26 -05:00
|
|
|
import { ImmichLogger } from '@app/infra/logger';
|
|
|
|
|
import { newDatabaseRepositoryMock } from '@test';
|
|
|
|
|
|
|
|
|
|
describe(DatabaseService.name, () => {
|
|
|
|
|
let sut: DatabaseService;
|
|
|
|
|
let databaseMock: jest.Mocked<IDatabaseRepository>;
|
|
|
|
|
let fatalLog: jest.SpyInstance;
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
databaseMock = newDatabaseRepositoryMock();
|
|
|
|
|
fatalLog = jest.spyOn(ImmichLogger.prototype, 'fatal');
|
|
|
|
|
|
|
|
|
|
sut = new DatabaseService(databaseMock);
|
|
|
|
|
|
|
|
|
|
sut.minVectorsVersion = new Version(0, 1, 1);
|
|
|
|
|
sut.maxVectorsVersion = new Version(0, 1, 11);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
fatalLog.mockRestore();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work', () => {
|
|
|
|
|
expect(sut).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('init', () => {
|
2024-01-07 01:53:09 +00:00
|
|
|
it('should resolve successfully if minimum supported PostgreSQL and vectors version are installed', async () => {
|
2024-01-07 00:24:09 +00:00
|
|
|
databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(14, 0, 0));
|
|
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(new Version(0, 1, 1));
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
await expect(sut.init()).resolves.toBeUndefined();
|
2024-01-07 00:24:09 +00:00
|
|
|
|
|
|
|
|
expect(databaseMock.getPostgresVersion).toHaveBeenCalledTimes(2);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledWith(DatabaseExtension.VECTORS);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(fatalLog).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
it('should throw an error if PostgreSQL version is below minimum supported version', async () => {
|
2024-01-07 00:24:09 +00:00
|
|
|
databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(13, 0, 0));
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
await expect(sut.init()).rejects.toThrow(/PostgreSQL version is 13/s);
|
2024-01-07 00:24:09 +00:00
|
|
|
|
|
|
|
|
expect(databaseMock.getPostgresVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
it('should resolve successfully if minimum supported vectors version is installed', async () => {
|
2023-12-21 11:06:26 -05:00
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(new Version(0, 1, 1));
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
await expect(sut.init()).resolves.toBeUndefined();
|
2023-12-21 11:06:26 -05:00
|
|
|
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledWith(DatabaseExtension.VECTORS);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(fatalLog).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
it('should resolve successfully if maximum supported vectors version is installed', async () => {
|
2023-12-21 11:06:26 -05:00
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(new Version(0, 1, 11));
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
await expect(sut.init()).resolves.toBeUndefined();
|
2023-12-21 11:06:26 -05:00
|
|
|
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledWith(DatabaseExtension.VECTORS);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(fatalLog).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw an error if vectors version is not installed even after createVectors', async () => {
|
|
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(null);
|
|
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow('Unexpected: The pgvecto.rs extension is not installed.');
|
|
|
|
|
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw an error if vectors version is below minimum supported version', async () => {
|
|
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(new Version(0, 0, 1));
|
|
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(/('tensorchord\/pgvecto-rs:pg14-v0.1.11')/s);
|
|
|
|
|
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw an error if vectors version is above maximum supported version', async () => {
|
|
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(new Version(0, 1, 12));
|
|
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(
|
|
|
|
|
/('DROP EXTENSION IF EXISTS vectors').*('tensorchord\/pgvecto-rs:pg14-v0\.1\.11')/s,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw an error if vectors version is a nightly', async () => {
|
|
|
|
|
databaseMock.getExtensionVersion.mockResolvedValueOnce(new Version(0, 0, 0));
|
|
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(
|
|
|
|
|
/(nightly).*('DROP EXTENSION IF EXISTS vectors').*('tensorchord\/pgvecto-rs:pg14-v0\.1\.11')/s,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(databaseMock.getExtensionVersion).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw error if vectors extension could not be created', async () => {
|
|
|
|
|
databaseMock.createExtension.mockRejectedValueOnce(new Error('Failed to create extension'));
|
|
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow('Failed to create extension');
|
|
|
|
|
|
|
|
|
|
expect(fatalLog).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(fatalLog.mock.calls[0][0]).toMatch(/('tensorchord\/pgvecto-rs:pg14-v0\.1\.11').*(v1\.91\.0)/s);
|
|
|
|
|
expect(databaseMock.createExtension).toHaveBeenCalledTimes(1);
|
|
|
|
|
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
it.each([{ major: 14 }, { major: 15 }, { major: 16 }])(
|
|
|
|
|
`should suggest image with postgres $major if database is $major`,
|
|
|
|
|
async ({ major }) => {
|
2023-12-21 11:06:26 -05:00
|
|
|
databaseMock.getExtensionVersion.mockResolvedValue(new Version(0, 0, 1));
|
2024-01-07 00:24:09 +00:00
|
|
|
databaseMock.getPostgresVersion.mockResolvedValue(new Version(major, 0, 0));
|
2023-12-21 11:06:26 -05:00
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(new RegExp(`tensorchord\/pgvecto-rs:pg${major}-v0\\.1\\.11`, 's'));
|
2024-01-07 01:53:09 +00:00
|
|
|
},
|
|
|
|
|
);
|
2023-12-21 11:06:26 -05:00
|
|
|
|
|
|
|
|
it('should not suggest image if postgres version is not in 14, 15 or 16', async () => {
|
2024-01-07 00:24:09 +00:00
|
|
|
databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(17, 0, 0));
|
|
|
|
|
databaseMock.getPostgresVersion.mockResolvedValueOnce(new Version(17, 0, 0));
|
2023-12-21 11:06:26 -05:00
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(/^(?:(?!tensorchord\/pgvecto-rs).)*$/s);
|
|
|
|
|
});
|
|
|
|
|
|
2024-01-07 01:53:09 +00:00
|
|
|
it('should reject and suggest the maximum supported version when unsupported pgvecto.rs version is in use', async () => {
|
2023-12-21 11:06:26 -05:00
|
|
|
databaseMock.getExtensionVersion.mockResolvedValue(new Version(0, 0, 1));
|
|
|
|
|
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(/('tensorchord\/pgvecto-rs:pg14-v0\.1\.11')/s);
|
|
|
|
|
|
|
|
|
|
sut.maxVectorsVersion = new Version(0, 1, 12);
|
|
|
|
|
await expect(sut.init()).rejects.toThrow(/('tensorchord\/pgvecto-rs:pg14-v0\.1\.12')/s);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|