import Location from '../../../src/models/Location'; import { createTestDatabase } from '../../setup'; import { Database } from 'sqlite3'; describe('Location Model', () => { let db: Database; let locationModel: Location; beforeEach(async () => { db = await createTestDatabase(); locationModel = new Location(db); }); afterEach((done) => { db.close(done); }); describe('create', () => { it('should create a new location with all fields', async () => { const locationData = { address: '123 Main St, Grand Rapids, MI', latitude: 42.9634, longitude: -85.6681, description: 'Black ice present' }; const result = await locationModel.create(locationData); expect(result).toMatchObject({ id: 1, address: locationData.address, latitude: locationData.latitude, longitude: locationData.longitude, description: locationData.description }); }); it('should create a location with only required address field', async () => { const locationData = { address: 'Main St & Oak Ave' }; const result = await locationModel.create(locationData); expect(result).toMatchObject({ id: 1, address: locationData.address }); }); it('should handle undefined optional fields', async () => { const locationData = { address: 'Test Address', latitude: undefined, longitude: undefined, description: undefined }; const result = await locationModel.create(locationData); expect(result).toMatchObject({ id: 1, address: locationData.address }); }); }); describe('getActive', () => { beforeEach(async () => { // Insert test data with different timestamps const now = new Date(); const twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000); const fiftyHoursAgo = new Date(now.getTime() - 50 * 60 * 60 * 1000); // Active location (within 48 hours) await new Promise((resolve, reject) => { db.run( 'INSERT INTO locations (address, created_at) VALUES (?, ?)', ['Active Location', twoHoursAgo.toISOString()], (err) => err ? reject(err) : resolve() ); }); // Expired location (over 48 hours) await new Promise((resolve, reject) => { db.run( 'INSERT INTO locations (address, created_at) VALUES (?, ?)', ['Expired Location', fiftyHoursAgo.toISOString()], (err) => err ? reject(err) : resolve() ); }); // Persistent location (expired but persistent) await new Promise((resolve, reject) => { db.run( 'INSERT INTO locations (address, created_at, persistent) VALUES (?, ?, ?)', ['Persistent Location', fiftyHoursAgo.toISOString(), 1], (err) => err ? reject(err) : resolve() ); }); }); it('should return only active and persistent locations', async () => { const activeLocations = await locationModel.getActive(); expect(activeLocations).toHaveLength(2); expect(activeLocations.map(l => l.address)).toContain('Active Location'); expect(activeLocations.map(l => l.address)).toContain('Persistent Location'); expect(activeLocations.map(l => l.address)).not.toContain('Expired Location'); }); it('should respect custom hours threshold', async () => { const activeLocations = await locationModel.getActive(1); // 1 hour threshold expect(activeLocations).toHaveLength(1); expect(activeLocations[0].address).toBe('Persistent Location'); }); }); describe('getAll', () => { beforeEach(async () => { await locationModel.create({ address: 'Location 1' }); await locationModel.create({ address: 'Location 2' }); await locationModel.create({ address: 'Location 3' }); }); it('should return all locations', async () => { const allLocations = await locationModel.getAll(); expect(allLocations).toHaveLength(3); expect(allLocations.map(l => l.address)).toEqual( expect.arrayContaining(['Location 1', 'Location 2', 'Location 3']) ); }); it('should return locations in reverse chronological order', async () => { const allLocations = await locationModel.getAll(); // Check that we have all locations and they're ordered by created_at DESC expect(allLocations).toHaveLength(3); // The query uses ORDER BY created_at DESC, so the most recent should be first // Since they're created in the same moment, check that ordering is consistent expect(allLocations[0]).toHaveProperty('id'); expect(allLocations[1]).toHaveProperty('id'); expect(allLocations[2]).toHaveProperty('id'); }); }); describe('update', () => { let locationId: number; beforeEach(async () => { const location = await locationModel.create({ address: 'Original Address', description: 'Original Description' }); locationId = location.id; }); it('should update a location successfully', async () => { const updateData = { address: 'Updated Address', latitude: 42.9634, longitude: -85.6681, description: 'Updated Description' }; const result = await locationModel.update(locationId, updateData); expect(result.changes).toBe(1); }); it('should return 0 changes for non-existent location', async () => { const updateData = { address: 'Updated Address' }; const result = await locationModel.update(99999, updateData); expect(result.changes).toBe(0); }); }); describe('togglePersistent', () => { let locationId: number; beforeEach(async () => { const location = await locationModel.create({ address: 'Test Location' }); locationId = location.id; }); it('should toggle persistent to true', async () => { const result = await locationModel.togglePersistent(locationId, true); expect(result.changes).toBe(1); }); it('should toggle persistent to false', async () => { const result = await locationModel.togglePersistent(locationId, false); expect(result.changes).toBe(1); }); it('should return 0 changes for non-existent location', async () => { const result = await locationModel.togglePersistent(99999, true); expect(result.changes).toBe(0); }); }); describe('delete', () => { let locationId: number; beforeEach(async () => { const location = await locationModel.create({ address: 'To Be Deleted' }); locationId = location.id; }); it('should delete a location successfully', async () => { const result = await locationModel.delete(locationId); expect(result.changes).toBe(1); }); it('should return 0 changes for non-existent location', async () => { const result = await locationModel.delete(99999); expect(result.changes).toBe(0); }); }); describe('cleanupExpired', () => { beforeEach(async () => { const now = new Date(); const fiftyHoursAgo = new Date(now.getTime() - 50 * 60 * 60 * 1000); // Expired non-persistent location await new Promise((resolve, reject) => { db.run( 'INSERT INTO locations (address, created_at, persistent) VALUES (?, ?, ?)', ['Expired Non-Persistent', fiftyHoursAgo.toISOString(), 0], (err) => err ? reject(err) : resolve() ); }); // Expired persistent location await new Promise((resolve, reject) => { db.run( 'INSERT INTO locations (address, created_at, persistent) VALUES (?, ?, ?)', ['Expired Persistent', fiftyHoursAgo.toISOString(), 1], (err) => err ? reject(err) : resolve() ); }); // Recent location await locationModel.create({ address: 'Recent Location' }); }); it('should delete only expired non-persistent locations', async () => { const result = await locationModel.cleanupExpired(); expect(result.changes).toBe(1); const remaining = await locationModel.getAll(); expect(remaining).toHaveLength(2); expect(remaining.map(l => l.address)).toContain('Expired Persistent'); expect(remaining.map(l => l.address)).toContain('Recent Location'); }); }); });