localgreenchain/__tests__/api/transport.test.ts
Claude b8d2d5be5f
Add comprehensive testing suite for transport, demand, and vertical farming systems
- Set up Jest testing framework with TypeScript support
- Add unit tests for TransportChain blockchain (tracker, carbon, types)
- Add unit tests for DemandForecaster (forecaster, aggregation, recommendations)
- Add unit tests for VerticalFarmController (controller, recipes, environment)
- Add API tests for transport, demand, and vertical-farm endpoints
- Add integration tests for full lifecycle workflows:
  - Seed-to-seed lifecycle
  - Demand-to-harvest flow
  - VF batch lifecycle
2025-11-22 18:47:04 +00:00

325 lines
9.6 KiB
TypeScript

/**
* 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,
};
}