/** * Transport API Tests * Tests for transport-related API endpoints * * Note: These tests are designed to test API route handlers. * They mock the underlying services and test request/response handling. */ import { TransportChain, getTransportChain, setTransportChain } from '../../lib/transport/tracker'; import { SeedAcquisitionEvent, PlantingEvent, HarvestEvent, TransportLocation, } from '../../lib/transport/types'; describe('Transport API', () => { let chain: TransportChain; beforeEach(() => { chain = new TransportChain(1); setTransportChain(chain); }); describe('POST /api/transport/seed-acquisition', () => { it('should record seed acquisition event', () => { const event: SeedAcquisitionEvent = { id: 'api-seed-001', timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank'), toLocation: createLocation('greenhouse'), distanceKm: 25, durationMinutes: 45, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'seed-bank-1', receiverId: 'grower-1', status: 'verified', seedBatchId: 'batch-001', sourceType: 'seed_bank', species: 'Solanum lycopersicum', quantity: 100, quantityUnit: 'seeds', generation: 1, }; const block = chain.recordEvent(event); expect(block.transportEvent.id).toBe('api-seed-001'); expect(block.transportEvent.eventType).toBe('seed_acquisition'); }); it('should reject invalid seed acquisition data', () => { const invalidEvent = { id: 'invalid-001', eventType: 'seed_acquisition', // Missing required fields }; expect(() => { chain.recordEvent(invalidEvent as any); }).toThrow(); }); }); describe('POST /api/transport/planting', () => { it('should record planting event', () => { const event: PlantingEvent = { id: 'api-planting-001', timestamp: new Date().toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse'), toLocation: createLocation('greenhouse'), distanceKm: 0, durationMinutes: 10, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-1', receiverId: 'grower-1', status: 'verified', seedBatchId: 'batch-001', plantIds: ['plant-001', 'plant-002'], plantingMethod: 'indoor_start', quantityPlanted: 2, growingEnvironment: 'greenhouse', }; const block = chain.recordEvent(event); expect(block.transportEvent.eventType).toBe('planting'); expect((block.transportEvent as PlantingEvent).plantIds.length).toBe(2); }); }); describe('POST /api/transport/harvest', () => { it('should record harvest event', () => { const event: HarvestEvent = { id: 'api-harvest-001', timestamp: new Date().toISOString(), eventType: 'harvest', fromLocation: createLocation('farm'), toLocation: createLocation('warehouse'), distanceKm: 10, durationMinutes: 30, transportMethod: 'electric_truck', carbonFootprintKg: 0, senderId: 'grower-1', receiverId: 'distributor-1', status: 'verified', plantIds: ['plant-001'], harvestBatchId: 'harvest-001', harvestType: 'full', produceType: 'tomatoes', grossWeight: 10, netWeight: 9.5, weightUnit: 'kg', packagingType: 'crates', temperatureRequired: { min: 10, max: 15, optimal: 12, unit: 'celsius' }, shelfLifeHours: 168, seedsSaved: false, }; const block = chain.recordEvent(event); expect(block.transportEvent.eventType).toBe('harvest'); }); }); describe('GET /api/transport/journey/[plantId]', () => { it('should return plant journey', () => { const plantId = 'journey-plant-001'; // Record events for plant const plantingEvent: PlantingEvent = { id: 'journey-planting-001', timestamp: new Date().toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse'), toLocation: createLocation('greenhouse'), distanceKm: 0, durationMinutes: 5, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-1', receiverId: 'grower-1', status: 'verified', seedBatchId: 'batch-001', plantIds: [plantId], plantingMethod: 'indoor_start', quantityPlanted: 1, growingEnvironment: 'greenhouse', }; chain.recordEvent(plantingEvent); const journey = chain.getPlantJourney(plantId); expect(journey).not.toBeNull(); expect(journey?.plantId).toBe(plantId); expect(journey?.events.length).toBe(1); }); it('should return null for unknown plant', () => { const journey = chain.getPlantJourney('unknown-plant-id'); expect(journey).toBeNull(); }); }); describe('GET /api/transport/footprint/[userId]', () => { it('should return environmental impact', () => { const userId = 'footprint-user-001'; const event: SeedAcquisitionEvent = { id: 'footprint-event-001', timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank'), toLocation: createLocation('greenhouse'), distanceKm: 50, durationMinutes: 60, transportMethod: 'diesel_truck', carbonFootprintKg: 0, senderId: userId, receiverId: userId, status: 'verified', seedBatchId: 'batch-footprint', sourceType: 'seed_bank', species: 'Test species', quantity: 100, quantityUnit: 'seeds', generation: 1, }; chain.recordEvent(event); const impact = chain.getEnvironmentalImpact(userId); expect(impact.totalFoodMiles).toBe(50); expect(impact.totalCarbonKg).toBeGreaterThan(0); expect(impact.comparisonToConventional).toBeDefined(); }); it('should return zero impact for user with no events', () => { const impact = chain.getEnvironmentalImpact('no-events-user'); expect(impact.totalFoodMiles).toBe(0); expect(impact.totalCarbonKg).toBe(0); }); }); describe('GET /api/transport/verify/[blockHash]', () => { it('should verify chain integrity', () => { chain.recordEvent(createSeedEvent()); chain.recordEvent(createSeedEvent()); const isValid = chain.isChainValid(); expect(isValid).toBe(true); }); it('should detect tampered chain', () => { chain.recordEvent(createSeedEvent()); // Tamper with block chain.chain[1].transportEvent.distanceKm = 999999; const isValid = chain.isChainValid(); expect(isValid).toBe(false); }); }); describe('GET /api/transport/qr/[id]', () => { it('should generate QR data for plant', () => { const plantId = 'qr-plant-001'; const plantingEvent: PlantingEvent = { id: 'qr-planting-001', timestamp: new Date().toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse'), toLocation: createLocation('greenhouse'), distanceKm: 0, durationMinutes: 5, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-1', receiverId: 'grower-1', status: 'verified', seedBatchId: 'batch-001', plantIds: [plantId], plantingMethod: 'indoor_start', quantityPlanted: 1, growingEnvironment: 'greenhouse', }; chain.recordEvent(plantingEvent); const qrData = chain.generateQRData(plantId, undefined); expect(qrData.plantId).toBe(plantId); expect(qrData.quickLookupUrl).toContain(plantId); expect(qrData.verificationCode).toBeDefined(); }); it('should generate QR data for batch', () => { const batchId = 'qr-batch-001'; const event = createSeedEvent(); event.seedBatchId = batchId; chain.recordEvent(event); const qrData = chain.generateQRData(undefined, batchId); expect(qrData.batchId).toBe(batchId); }); }); describe('Response Format', () => { it('should return blocks with all required fields', () => { const block = chain.recordEvent(createSeedEvent()); expect(block.index).toBeDefined(); expect(block.timestamp).toBeDefined(); expect(block.transportEvent).toBeDefined(); expect(block.previousHash).toBeDefined(); expect(block.hash).toBeDefined(); expect(block.nonce).toBeDefined(); expect(block.cumulativeCarbonKg).toBeDefined(); expect(block.cumulativeFoodMiles).toBeDefined(); }); }); }); // Helper functions function createLocation(type: string): TransportLocation { return { latitude: 40.7128, longitude: -74.006, locationType: type as any, facilityName: `Test ${type}`, }; } function createSeedEvent(): SeedAcquisitionEvent { return { id: `seed-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank'), toLocation: createLocation('greenhouse'), distanceKm: 25, durationMinutes: 45, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'seed-bank-1', receiverId: 'grower-1', status: 'verified', seedBatchId: `batch-${Date.now()}`, sourceType: 'seed_bank', species: 'Test species', quantity: 100, quantityUnit: 'seeds', generation: 1, }; }