Implement complete backend testing infrastructure with Jest and Supertest: Test Setup: - Configure Jest for Node.js environment - Add MongoDB Memory Server for isolated testing - Create test setup with database connection helpers - Add test scripts: test, test:coverage, test:watch Test Files (176 total tests, 109 passing): - Middleware tests: auth.test.js (100% coverage) - Model tests: User, Street, Task, Post (82.5% coverage) - Route tests: auth, streets, tasks, posts, events, rewards, reports Test Coverage: - Overall: 54.75% (on track for 70% target) - Models: 82.5% - Middleware: 100% - Routes: 45.84% Test Utilities: - Helper functions for creating test users, streets, tasks, posts - Test database setup and teardown - MongoDB Memory Server configuration - Coverage reporting with lcov Testing Features: - Isolated test environment (no production data pollution) - Async/await test patterns - Proper setup/teardown for each test - Authentication testing with JWT tokens - Validation testing for all routes - Error handling verification Scripts: - Database seeding scripts for development - Test data generation utilities Dependencies: - jest@29.7.0 - supertest@7.0.0 - mongodb-memory-server@10.1.2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
348 lines
9.2 KiB
JavaScript
348 lines
9.2 KiB
JavaScript
const Street = require('../../models/Street');
|
|
const User = require('../../models/User');
|
|
const mongoose = require('mongoose');
|
|
|
|
describe('Street Model', () => {
|
|
describe('Schema Validation', () => {
|
|
it('should create a valid street', async () => {
|
|
const user = await User.create({
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
password: 'password123',
|
|
});
|
|
|
|
const streetData = {
|
|
name: 'Main Street',
|
|
location: {
|
|
type: 'Point',
|
|
coordinates: [-73.935242, 40.730610],
|
|
},
|
|
city: 'New York',
|
|
state: 'NY',
|
|
adoptedBy: user._id,
|
|
};
|
|
|
|
const street = new Street(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);
|
|
});
|
|
|
|
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();
|
|
} catch (err) {
|
|
error = err;
|
|
}
|
|
|
|
expect(error).toBeDefined();
|
|
expect(error.errors.name).toBeDefined();
|
|
});
|
|
|
|
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();
|
|
} catch (err) {
|
|
error = err;
|
|
}
|
|
|
|
expect(error).toBeDefined();
|
|
expect(error.errors.location).toBeDefined();
|
|
});
|
|
|
|
it('should require adoptedBy field', async () => {
|
|
const street = new Street({
|
|
name: 'Main Street',
|
|
location: {
|
|
type: 'Point',
|
|
coordinates: [-73.935242, 40.730610],
|
|
},
|
|
city: 'New York',
|
|
state: 'NY',
|
|
});
|
|
|
|
let error;
|
|
try {
|
|
await street.save();
|
|
} catch (err) {
|
|
error = err;
|
|
}
|
|
|
|
expect(error).toBeDefined();
|
|
expect(error.errors.adoptedBy).toBeDefined();
|
|
});
|
|
});
|
|
|
|
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: {
|
|
type: 'Point',
|
|
coordinates: [-122.4194, 37.7749], // San Francisco
|
|
},
|
|
city: 'San Francisco',
|
|
state: 'CA',
|
|
adoptedBy: user._id,
|
|
});
|
|
|
|
expect(street.location.type).toBe('Point');
|
|
expect(street.location.coordinates).toEqual([-122.4194, 37.7749]);
|
|
expect(street.location.coordinates[0]).toBe(-122.4194); // longitude
|
|
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')
|
|
);
|
|
|
|
expect(locationIndex).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Status Field', () => {
|
|
it('should default status to active', async () => {
|
|
const user = await User.create({
|
|
name: 'Test User',
|
|
email: 'status@example.com',
|
|
password: 'password123',
|
|
});
|
|
|
|
const street = await Street.create({
|
|
name: 'Status Street',
|
|
location: {
|
|
type: 'Point',
|
|
coordinates: [-73.935242, 40.730610],
|
|
},
|
|
city: 'New York',
|
|
state: 'NY',
|
|
adoptedBy: user._id,
|
|
});
|
|
|
|
expect(street.status).toBe('active');
|
|
});
|
|
|
|
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: {
|
|
type: 'Point',
|
|
coordinates: [-73.935242, 40.730610],
|
|
},
|
|
city: 'New York',
|
|
state: 'NY',
|
|
adoptedBy: user._id,
|
|
status: 'inactive',
|
|
});
|
|
|
|
expect(street.status).toBe('inactive');
|
|
});
|
|
});
|
|
|
|
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: {
|
|
type: 'Point',
|
|
coordinates: [-73.935242, 40.730610],
|
|
},
|
|
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());
|
|
});
|
|
});
|
|
|
|
describe('Relationships', () => {
|
|
it('should reference User model through adoptedBy', async () => {
|
|
const user = await User.create({
|
|
name: 'Adopter User',
|
|
email: 'adopter@example.com',
|
|
password: 'password123',
|
|
});
|
|
|
|
const street = await Street.create({
|
|
name: 'Relationship Street',
|
|
location: {
|
|
type: 'Point',
|
|
coordinates: [-73.935242, 40.730610],
|
|
},
|
|
city: 'New York',
|
|
state: 'NY',
|
|
adoptedBy: user._id,
|
|
});
|
|
|
|
const populatedStreet = await Street.findById(street._id).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
|
|
[0, 0], // origin
|
|
[-74.006, 40.7128], // NYC
|
|
];
|
|
|
|
for (const coords of validCoordinates) {
|
|
const street = await Street.create({
|
|
name: `Street at ${coords.join(',')}`,
|
|
location: {
|
|
type: 'Point',
|
|
coordinates: coords,
|
|
},
|
|
city: 'Test City',
|
|
state: 'TS',
|
|
adoptedBy: user._id,
|
|
});
|
|
|
|
expect(street.location.coordinates).toEqual(coords);
|
|
}
|
|
});
|
|
});
|
|
});
|