@joystick.js/db-canary
Version:
JoystickDB - A minimalist database server for the Joystick framework
220 lines (187 loc) • 6.16 kB
JavaScript
import test from 'ava';
import fs from 'fs';
import net from 'net';
import { create_server } from '../../../src/server/index.js';
import { setup_initial_admin, reset_auth_state } from '../../../src/server/lib/user_auth_manager.js';
import { initialize_database } from '../../../src/server/lib/query_engine.js';
import { encode_message, create_message_parser } from '../../../src/server/lib/tcp_protocol.js';
const AUTH_FILE_PATH = './settings.db.json';
// Shared server instance
let shared_server = null;
let shared_port = null;
let admin_username = 'admin';
let admin_password = 'admin123';
test.before(async () => {
// Set test environment
process.env.NODE_ENV = 'test';
// Reset auth state and clean up any existing auth files
await reset_auth_state();
if (fs.existsSync(AUTH_FILE_PATH)) {
fs.unlinkSync(AUTH_FILE_PATH);
}
// Initialize database for testing
initialize_database();
// Create shared server instance
shared_server = await create_server();
// Start server on random port
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Server start timeout'));
}, 5000);
shared_server.listen(0, (error) => {
clearTimeout(timeout);
if (error) {
reject(error);
} else {
shared_port = shared_server.address().port;
resolve();
}
});
});
// Set up initial admin user
await setup_initial_admin(admin_username, admin_password, 'admin@test.com');
});
test.after.always(async () => {
// Clean up shared server
if (shared_server) {
try {
if (shared_server.cleanup) {
await shared_server.cleanup();
}
} catch (error) {
// Ignore cleanup errors
}
await new Promise((resolve) => {
const timeout = setTimeout(() => {
try {
shared_server.close();
} catch (error) {
// Ignore errors
}
resolve();
}, 2000);
try {
shared_server.close(() => {
clearTimeout(timeout);
resolve();
});
} catch (error) {
clearTimeout(timeout);
resolve();
}
});
shared_server = null;
}
// Reset auth state and clean up auth files
await reset_auth_state();
if (fs.existsSync(AUTH_FILE_PATH)) {
try {
fs.unlinkSync(AUTH_FILE_PATH);
} catch (error) {
// Ignore cleanup errors
}
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
});
test.beforeEach(async () => {
// Ensure admin user is set up for each test
await setup_initial_admin(admin_username, admin_password, 'admin@test.com');
});
const create_client = () => {
return new Promise((resolve, reject) => {
const client = net.createConnection(shared_port, 'localhost');
const parser = create_message_parser();
client.on('connect', () => {
resolve({
client,
send: (data) => {
const encoded = encode_message(data);
client.write(encoded);
},
receive: () => {
return new Promise((resolve) => {
const handler = (data) => {
try {
const messages = parser.parse_messages(data);
for (const message of messages) {
client.off('data', handler);
resolve(message);
return;
}
} catch (error) {
// Continue listening
}
};
client.on('data', handler);
});
},
close: () => {
client.end();
}
});
});
client.on('error', reject);
});
};
test('replication admin operations - get status when disabled', async t => {
const { client, send, receive, close } = await create_client();
try {
// First authenticate with username and password
send({ op: 'authentication', data: { username: admin_username, password: admin_password } });
const auth_response = await receive();
t.is(auth_response.ok, 1);
// Get admin status
send({ op: 'admin' });
const admin_response = await receive();
// Admin response should include server info
t.is(typeof admin_response.server, 'object');
t.is(typeof admin_response.database, 'object');
t.is(typeof admin_response.authentication, 'object');
} finally {
close();
}
});
test('replication integration - basic server functionality works', async t => {
const { client, send, receive, close } = await create_client();
try {
// First authenticate with username and password
send({ op: 'authentication', data: { username: admin_username, password: admin_password } });
const auth_response = await receive();
t.is(auth_response.ok, 1);
// Test basic ping operation
send({ op: 'ping' });
const ping_response = await receive();
t.is(ping_response.ok, 1);
// Test admin operation works
send({ op: 'admin' });
const admin_response = await receive();
// Admin response should include basic server info
t.is(typeof admin_response.server, 'object');
t.is(typeof admin_response.database, 'object');
} finally {
close();
}
});
test('replication integration - database operations work normally', async t => {
const { client, send, receive, close } = await create_client();
try {
// First authenticate with username and password
send({ op: 'authentication', data: { username: admin_username, password: admin_password } });
const auth_response = await receive();
t.is(auth_response.ok, 1);
// Test insert operation
send({ op: 'insert_one', data: { collection: 'test', document: { name: 'test', value: 1 } } });
const insert_response = await receive();
t.is(insert_response.ok, 1);
// Test find operation
send({ op: 'find_one', data: { collection: 'test', filter: { name: 'test' } } });
const find_response = await receive();
t.is(find_response.ok, 1);
t.is(find_response.document.name, 'test');
} finally {
close();
}
});