docudb
Version:
Document-based NoSQL database for NodeJS
425 lines • 16.7 kB
JavaScript
import { Database, Schema, Query } from '../index.js';
import { expect } from 'chai';
import { cleanTestDataDir } from './utils.js';
describe('DocuDB - Schema Validation', () => {
let db;
let productos;
const testDbName = 'testSchema';
beforeEach(async () => {
await cleanTestDataDir(testDbName);
db = new Database({
name: testDbName,
compression: false
});
await db.initialize();
const productoSchema = new Schema({
name: {
type: 'string',
required: true,
validate: { minLength: 3, maxLength: 50 }
},
price: {
type: 'number',
required: true,
validate: { min: 0 }
},
categorias: {
type: 'array',
validate: { minLength: 1 }
}
}, {
strict: true
});
productos = db.collection('productos', { schema: productoSchema });
});
afterEach(async () => {
await cleanTestDataDir(testDbName);
});
describe('Schema Validation', () => {
it('should reject invalid document structure', async () => {
try {
await productos.insertOne({
name: 'Test Product',
price: 15,
invalidField: 'test'
});
expect.fail('Should have thrown validation error');
}
catch (error) {
expect(error.message).to.include('invalidField');
}
});
it('should validate string length', async () => {
try {
await productos.insertOne({
name: 'AB', // Less than minLength
price: 10,
categorias: ['test']
});
expect.fail('Should have thrown validation error');
}
catch (error) {
expect(error.message).to.include('greater than or equal to 3');
}
});
it('should validate number min value', async () => {
try {
await productos.insertOne({
name: 'Test Product',
price: -5, // Less than min
categorias: ['test']
});
expect.fail('Should have thrown validation error');
}
catch (error) {
expect(error.message).to.include('greater than or equal to 0');
}
});
it('should validate array length', async () => {
try {
await productos.insertOne({
name: 'Test Product',
price: 10,
categorias: [] // Empty array
});
expect.fail('Should have thrown validation error');
}
catch (error) {
expect(error.message).to.include('greater than or equal to 1');
}
});
});
});
describe('DocuDB - Schema Data Type Preservation', () => {
describe('Basic Type Validation', () => {
let schema;
beforeEach(() => {
schema = new Schema({
stringField: { type: 'string' },
numberField: { type: 'number' },
booleanField: { type: 'boolean' },
dateField: { type: 'date' },
arrayField: { type: 'array' },
objectField: { type: 'object' }
});
});
it('should preserve string', () => {
const doc = {
stringField: 'test string'
};
const validated = schema.validate(doc);
expect(typeof validated.stringField).equal('string');
expect(validated.stringField).equal('test string');
});
it('should preserve number', () => {
const doc = {
numberField: 42.5
};
const validated = schema.validate(doc);
expect(typeof validated.numberField).equal('number');
expect(validated.numberField).equal(42.5);
});
it('should preserve boolean', () => {
const doc = {
booleanField: true
};
const validated = schema.validate(doc);
expect(typeof validated.booleanField).equal('boolean');
expect(validated.booleanField).equal(true);
});
it('should preserve date', () => {
const date = new Date();
const doc = {
dateField: date
};
const validated = schema.validate(doc);
expect(validated.dateField instanceof Date).equal(true);
expect(validated.dateField.getTime()).equal(date.getTime());
});
it('should preserve array', () => {
const doc = {
arrayField: [1, 'test', { key: 'value' }]
};
const validated = schema.validate(doc);
expect(Array.isArray(validated.arrayField)).to.be.true;
expect(validated.arrayField).to.deep.equal([1, 'test', { key: 'value' }]);
});
it('should preserve object', () => {
const doc = {
objectField: { key1: 'value1', key2: 42 }
};
const validated = schema.validate(doc);
expect(typeof validated.objectField).equal('object');
expect(validated.objectField).to.deep.equal({ key1: 'value1', key2: 42 });
});
});
describe('Special Cases', () => {
it('should reject incorrect type', () => {
const schema = new Schema({
numberField: { type: 'number' }
});
expect(() => {
schema.validate({ numberField: 'not a number' });
}).to.throw();
});
it('should handle null and undefined values', () => {
const schema = new Schema({
optionalString: { type: 'string' },
requiredString: { type: 'string', required: true }
});
const doc = {
requiredString: 'present'
};
const validated = schema.validate(doc);
expect(validated.optionalString).to.be.undefined;
expect(validated.requiredString).equal('present');
});
it('should apply default values', () => {
const schema = new Schema({
defaultString: { type: 'string', default: 'default value' },
defaultNumber: { type: 'number', default: 0 },
defaultDate: { type: 'date', default: () => new Date(2023, 0, 1) }
});
const validated = schema.validate({});
expect(validated.defaultString).equal('default value');
expect(validated.defaultNumber).equal(0);
expect(validated.defaultDate.getFullYear()).equal(2023);
});
});
});
describe('DocuDB - Query Operations', () => {
let db;
let productos;
const testDbName = 'testQuery';
beforeEach(async () => {
await cleanTestDataDir(testDbName);
db = new Database({
name: testDbName,
compression: false
});
await db.initialize();
const productoSchema = new Schema({
name: { type: 'string', required: true },
price: { type: 'number', required: true },
stock: { type: 'number', default: 0 },
categorias: { type: 'array' }
});
productos = db.collection('productos', { schema: productoSchema });
// Insert test data
await productos.insertMany([
{
name: 'Laptop',
price: 1000,
stock: 5,
categorias: ['Electronics', 'Computers']
},
{
name: 'Mouse',
price: 20,
stock: 10,
categorias: ['Electronics', 'Peripherals']
},
{
name: 'Keyboard',
price: 50,
stock: 8,
categorias: ['Electronics', 'Peripherals']
},
{
name: 'Monitor',
price: 300,
stock: 3,
categorias: ['Electronics', 'Displays']
},
{
name: 'Tablet',
price: 250,
stock: 0,
categorias: ['Electronics', 'Mobile']
}
]);
});
afterEach(async () => {
await cleanTestDataDir(testDbName);
});
describe('Basic Queries', () => {
it('should find documents with $gt operator', async () => {
const results = await productos.find({ price: { $gt: 100 } });
expect(results).to.have.lengthOf(3);
expect(results.map(p => p.name)).to.have.members([
'Laptop',
'Monitor',
'Tablet'
]);
});
it('should find documents with $lt operator', async () => {
const results = await productos.find({ price: { $lt: 100 } });
expect(results).to.have.lengthOf(2);
expect(results.map(p => p.name)).to.have.members(['Mouse', 'Keyboard']);
});
it('should find documents with $in operator', async () => {
const results = await productos.find({
categorias: { $in: ['Peripherals'] }
});
expect(results).to.have.lengthOf(2);
expect(results.map(p => p.name)).to.have.members(['Mouse', 'Keyboard']);
});
});
describe('Complex Queries', () => {
it('should handle $and operator', async () => {
const query = new Query({
$and: [{ price: { $gt: 50 } }, { stock: { $gt: 0 } }]
});
const results = await productos.find(query);
expect(results).to.have.lengthOf(2);
expect(results.map(p => p.name)).to.have.members(['Laptop', 'Monitor']);
});
it('should handle $or operator', async () => {
const query = new Query({
$or: [{ price: { $lt: 30 } }, { stock: { $eq: 0 } }]
});
const results = await productos.find(query);
expect(results).to.have.lengthOf(2);
expect(results.map(p => p.name)).to.have.members(['Mouse', 'Tablet']);
});
it('should handle $not operator', async () => {
const query = new Query({
$not: { price: { $lt: 30 } }
});
const results = await productos.find(query);
expect(results).to.have.lengthOf(4);
expect(results.map(p => p.name)).to.have.members(['Laptop', 'Keyboard', 'Monitor', 'Tablet']);
});
it('should handle sorting', async () => {
const query = new Query({}).sort({ price: 1 });
const results = await productos.find(query);
expect(results[0].name).to.equal('Mouse');
expect(results[4].name).to.equal('Laptop');
});
it('should handle limit', async () => {
const query = new Query({}).limit(2);
const results = await productos.find(query);
expect(results).to.have.lengthOf(2);
});
});
});
describe('DocuDB - Data Type Preservation After Query Operations', () => {
let db;
let products;
const testDbName = 'testDataTypes';
beforeEach(async () => {
await cleanTestDataDir(testDbName);
db = new Database({
name: testDbName,
compression: false
});
await db.initialize();
const productSchema = new Schema({
name: { type: 'string', required: true },
price: { type: 'number', required: true },
stock: { type: 'number', default: 0 },
lastUpdated: { type: 'date' },
tags: { type: 'array', default: [] },
metadata: { type: 'object', default: {} }
});
products = db.collection('products', { schema: productSchema });
// Insert test data
await products.insertOne({
name: 'Test Product',
price: 100,
stock: 5,
lastUpdated: new Date(),
tags: ['test', 'product'],
metadata: { origin: 'local' }
});
});
afterEach(async () => {
await cleanTestDataDir(testDbName);
});
describe('Data Type Preservation After Find Operations', () => {
it('should maintain data types after find operation', async () => {
const result = await products.findOne({ name: 'Test Product' });
expect(result).to.exist;
if (result != null) {
expect(typeof result.name).to.equal('string');
expect(typeof result.price).to.equal('number');
expect(typeof result.stock).to.equal('number');
expect(result.lastUpdated).to.be.instanceOf(Date);
expect(Array.isArray(result.tags)).to.be.true;
expect(typeof result.metadata).to.equal('object');
expect(result.metadata).to.not.be.an('array');
}
});
it('should maintain data types after complex query operations', async () => {
const result = await products.findOne({
price: { $gte: 50 },
tags: { $in: ['test'] }
});
expect(result).to.exist;
if (result != null) {
expect(typeof result.name).to.equal('string');
expect(typeof result.price).to.equal('number');
expect(result.lastUpdated).to.be.instanceOf(Date);
expect(Array.isArray(result.tags)).to.be.true;
}
});
});
describe('Data Type Preservation After Update Operations', () => {
it('should maintain data types after full document update', async () => {
const doc = await products.findOne({ name: 'Test Product' });
expect(doc).to.exist;
if (doc != null) {
const updated = await products.updateById(doc._id, {
$set: {
price: 150,
stock: 10,
lastUpdated: new Date(),
tags: ['test', 'updated'],
metadata: { status: 'updated' }
}
});
expect(updated).to.exist;
if (updated != null) {
expect(typeof updated.price).to.equal('number');
expect(typeof updated.stock).to.equal('number');
expect(updated.lastUpdated).to.be.instanceOf(Date);
expect(Array.isArray(updated.tags)).to.be.true;
expect(typeof updated.metadata).to.equal('object');
expect(updated.metadata).to.not.be.an('array');
}
// Verify data persistence
const retrieved = await products.findById(doc._id);
expect(retrieved).to.exist;
if (retrieved != null) {
expect(typeof retrieved.price).to.equal('number');
expect(typeof retrieved.stock).to.equal('number');
expect(retrieved.lastUpdated).to.be.instanceOf(Date);
expect(Array.isArray(retrieved.tags)).to.be.true;
expect(typeof retrieved.metadata).to.equal('object');
}
}
});
it('should maintain data types after partial update', async () => {
const doc = await products.findOne({ name: 'Test Product' });
expect(doc).to.exist;
if (doc != null) {
// Partial update
await products.updateById(doc._id, {
$set: {
price: 200
}
});
const updated = await products.findById(doc._id);
expect(updated).to.exist;
if (updated != null) {
expect(typeof updated.price).to.equal('number');
expect(typeof updated.stock).to.equal('number');
expect(updated.lastUpdated).to.be.instanceOf(Date);
expect(Array.isArray(updated.tags)).to.be.true;
expect(typeof updated.metadata).to.equal('object');
}
}
});
});
});
//# sourceMappingURL=schema.test.js.map