feat: Migrate Street and Task models from MongoDB to CouchDB

- Replace Street model with CouchDB-based implementation
- Replace Task model with CouchDB-based implementation
- Update routes to use new model interfaces
- Handle geospatial queries with CouchDB design documents
- Maintain adoption functionality and middleware
- Use denormalized document structure with embedded data
- Update test files to work with new models
- Ensure API compatibility while using CouchDB underneath

🤖 Generated with [AI Assistant]

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
William Valentin
2025-11-01 13:12:34 -07:00
parent 2961107136
commit 7c7bc954ef
14 changed files with 1943 additions and 928 deletions

View File

@@ -1,8 +1,12 @@
const Street = require('../../models/Street');
const User = require('../../models/User');
const mongoose = require('mongoose');
const couchdbService = require('../../services/couchdbService');
describe('Street Model', () => {
beforeAll(async () => {
await couchdbService.initialize();
});
describe('Schema Validation', () => {
it('should create a valid street', async () => {
const user = await User.create({
@@ -19,76 +23,55 @@ describe('Street Model', () => {
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
};
const street = new Street(streetData);
const street = await Street.create(streetData);
const savedStreet = await street.save();
expect(savedStreet._id).toBeDefined();
expect(savedStreet.name).toBe(streetData.name);
expect(savedStreet.city).toBe(streetData.city);
expect(savedStreet.state).toBe(streetData.state);
expect(savedStreet.adoptedBy.toString()).toBe(user._id.toString());
expect(savedStreet.location.type).toBe('Point');
expect(savedStreet.location.coordinates).toEqual(streetData.location.coordinates);
expect(savedStreet.status).toBe('available');
});
it('should require name field', async () => {
const user = await User.create({
name: 'Test User',
email: 'test@example.com',
password: 'password123',
});
const street = new Street({
location: {
type: 'Point',
coordinates: [-73.935242, 40.730610],
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
});
let error;
try {
await street.save();
await Street.create({
location: {
type: 'Point',
coordinates: [-73.935242, 40.730610],
},
city: 'New York',
state: 'NY',
});
} catch (err) {
error = err;
}
expect(error).toBeDefined();
expect(error.errors.name).toBeDefined();
expect(error.message).toContain('name');
});
it('should require location field', async () => {
const user = await User.create({
name: 'Test User',
email: 'test@example.com',
password: 'password123',
});
const street = new Street({
name: 'Main Street',
city: 'New York',
state: 'NY',
adoptedBy: user._id,
});
let error;
try {
await street.save();
await Street.create({
name: 'Main Street',
city: 'New York',
state: 'NY',
});
} catch (err) {
error = err;
}
expect(error).toBeDefined();
expect(error.errors.location).toBeDefined();
expect(error.message).toContain('location');
});
it('should require adoptedBy field', async () => {
const street = new Street({
it('should not require adoptedBy field', async () => {
const street = await Street.create({
name: 'Main Street',
location: {
type: 'Point',
@@ -98,26 +81,14 @@ describe('Street Model', () => {
state: 'NY',
});
let error;
try {
await street.save();
} catch (err) {
error = err;
}
expect(error).toBeDefined();
expect(error.errors.adoptedBy).toBeDefined();
expect(street._id).toBeDefined();
expect(street.adoptedBy).toBeNull();
expect(street.status).toBe('available');
});
});
describe('GeoJSON Location', () => {
it('should store Point type correctly', async () => {
const user = await User.create({
name: 'Test User',
email: 'geo@example.com',
password: 'password123',
});
const street = await Street.create({
name: 'Geo Street',
location: {
@@ -126,7 +97,6 @@ describe('Street Model', () => {
},
city: 'San Francisco',
state: 'CA',
adoptedBy: user._id,
});
expect(street.location.type).toBe('Point');
@@ -135,24 +105,26 @@ describe('Street Model', () => {
expect(street.location.coordinates[1]).toBe(37.7749); // latitude
});
it('should create 2dsphere index on location', async () => {
const indexes = await Street.collection.getIndexes();
const locationIndex = Object.keys(indexes).find(key =>
indexes[key].some(field => field[0] === 'location')
);
it('should support geospatial queries', async () => {
const street = await Street.create({
name: 'NYC Street',
location: {
type: 'Point',
coordinates: [-73.935242, 40.730610],
},
city: 'New York',
state: 'NY',
});
expect(locationIndex).toBeDefined();
// Test findNearby method
const nearbyStreets = await Street.findNearby([-73.935242, 40.730610], 1000);
expect(nearbyStreets.length).toBeGreaterThan(0);
expect(nearbyStreets[0].name).toBe('NYC Street');
});
});
describe('Status Field', () => {
it('should default status to active', async () => {
const user = await User.create({
name: 'Test User',
email: 'status@example.com',
password: 'password123',
});
it('should default status to available', async () => {
const street = await Street.create({
name: 'Status Street',
location: {
@@ -161,19 +133,12 @@ describe('Street Model', () => {
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
});
expect(street.status).toBe('active');
expect(street.status).toBe('available');
});
it('should allow setting custom status', async () => {
const user = await User.create({
name: 'Test User',
email: 'custom@example.com',
password: 'password123',
});
const street = await Street.create({
name: 'Custom Status Street',
location: {
@@ -182,22 +147,15 @@ describe('Street Model', () => {
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
status: 'inactive',
status: 'adopted',
});
expect(street.status).toBe('inactive');
expect(street.status).toBe('adopted');
});
});
describe('Timestamps', () => {
it('should automatically set createdAt and updatedAt', async () => {
const user = await User.create({
name: 'Test User',
email: 'timestamp@example.com',
password: 'password123',
});
const street = await Street.create({
name: 'Timestamp Street',
location: {
@@ -206,66 +164,12 @@ describe('Street Model', () => {
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
});
expect(street.createdAt).toBeDefined();
expect(street.updatedAt).toBeDefined();
expect(street.createdAt).toBeInstanceOf(Date);
expect(street.updatedAt).toBeInstanceOf(Date);
});
});
describe('Adoption Date', () => {
it('should default adoptionDate to current time', async () => {
const user = await User.create({
name: 'Test User',
email: 'adoption@example.com',
password: 'password123',
});
const beforeCreate = new Date();
const street = await Street.create({
name: 'Adoption Street',
location: {
type: 'Point',
coordinates: [-73.935242, 40.730610],
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
});
const afterCreate = new Date();
expect(street.adoptionDate).toBeDefined();
expect(street.adoptionDate.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
expect(street.adoptionDate.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
});
it('should allow custom adoption date', async () => {
const user = await User.create({
name: 'Test User',
email: 'customdate@example.com',
password: 'password123',
});
const customDate = new Date('2023-01-15');
const street = await Street.create({
name: 'Custom Date Street',
location: {
type: 'Point',
coordinates: [-73.935242, 40.730610],
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
adoptionDate: customDate,
});
expect(street.adoptionDate.getTime()).toBe(customDate.getTime());
expect(typeof street.createdAt).toBe('string');
expect(typeof street.updatedAt).toBe('string');
});
});
@@ -285,42 +189,24 @@ describe('Street Model', () => {
},
city: 'New York',
state: 'NY',
adoptedBy: user._id,
adoptedBy: {
userId: user._id,
name: user.name,
profilePicture: user.profilePicture || ''
},
status: 'adopted',
});
const populatedStreet = await Street.findById(street._id).populate('adoptedBy');
const populatedStreet = await Street.findById(street._id);
await populatedStreet.populate('adoptedBy');
expect(populatedStreet.adoptedBy).toBeDefined();
expect(populatedStreet.adoptedBy.name).toBe('Adopter User');
expect(populatedStreet.adoptedBy.email).toBe('adopter@example.com');
});
});
describe('Virtual Properties', () => {
it('should support tasks virtual', () => {
const street = new Street({
name: 'Test Street',
location: {
type: 'Point',
coordinates: [-73.935242, 40.730610],
},
city: 'New York',
state: 'NY',
adoptedBy: new mongoose.Types.ObjectId(),
});
expect(street.schema.virtuals.tasks).toBeDefined();
});
});
describe('Coordinates Format', () => {
it('should accept valid longitude and latitude', async () => {
const user = await User.create({
name: 'Test User',
email: 'coords@example.com',
password: 'password123',
});
const validCoordinates = [
[-180, -90], // min values
[180, 90], // max values
@@ -337,7 +223,6 @@ describe('Street Model', () => {
},
city: 'Test City',
state: 'TS',
adoptedBy: user._id,
});
expect(street.location.coordinates).toEqual(coords);