localgreenchain/__tests__/lib/transport/carbon.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

361 lines
12 KiB
TypeScript

/**
* Carbon Calculation Tests
* Tests for carbon footprint calculation logic
*/
import { TransportChain } from '../../../lib/transport/tracker';
import {
CARBON_FACTORS,
TransportMethod,
TransportLocation,
} from '../../../lib/transport/types';
describe('Carbon Footprint Calculation', () => {
let chain: TransportChain;
beforeEach(() => {
chain = new TransportChain(1);
});
describe('CARBON_FACTORS', () => {
it('should have zero emissions for walking', () => {
expect(CARBON_FACTORS.walking).toBe(0);
});
it('should have zero emissions for bicycle', () => {
expect(CARBON_FACTORS.bicycle).toBe(0);
});
it('should have low emissions for electric vehicles', () => {
expect(CARBON_FACTORS.electric_vehicle).toBeLessThan(CARBON_FACTORS.gasoline_vehicle);
expect(CARBON_FACTORS.electric_truck).toBeLessThan(CARBON_FACTORS.diesel_truck);
});
it('should have high emissions for air transport', () => {
expect(CARBON_FACTORS.air).toBeGreaterThan(CARBON_FACTORS.diesel_truck);
expect(CARBON_FACTORS.air).toBeGreaterThan(CARBON_FACTORS.ship);
});
it('should have moderate emissions for refrigerated trucks', () => {
expect(CARBON_FACTORS.refrigerated_truck).toBeGreaterThan(CARBON_FACTORS.diesel_truck);
});
it('should have lowest emissions for rail and ship', () => {
expect(CARBON_FACTORS.rail).toBeLessThan(CARBON_FACTORS.diesel_truck);
expect(CARBON_FACTORS.ship).toBeLessThan(CARBON_FACTORS.diesel_truck);
});
it('should define all transport methods', () => {
const methods: TransportMethod[] = [
'walking', 'bicycle', 'electric_vehicle', 'hybrid_vehicle',
'gasoline_vehicle', 'diesel_truck', 'electric_truck',
'refrigerated_truck', 'rail', 'ship', 'air', 'drone',
'local_delivery', 'customer_pickup'
];
methods.forEach(method => {
expect(CARBON_FACTORS[method]).toBeDefined();
expect(typeof CARBON_FACTORS[method]).toBe('number');
});
});
});
describe('calculateCarbon', () => {
it('should calculate zero carbon for zero distance', () => {
const carbon = chain.calculateCarbon('diesel_truck', 0, 10);
expect(carbon).toBe(0);
});
it('should calculate zero carbon for zero weight', () => {
const carbon = chain.calculateCarbon('diesel_truck', 100, 0);
expect(carbon).toBe(0);
});
it('should calculate zero carbon for walking', () => {
const carbon = chain.calculateCarbon('walking', 10, 5);
expect(carbon).toBe(0);
});
it('should calculate correct carbon for known values', () => {
// diesel_truck = 0.15 kg CO2 per km per kg cargo
const carbon = chain.calculateCarbon('diesel_truck', 100, 10);
expect(carbon).toBe(0.15 * 100 * 10);
});
it('should increase linearly with distance', () => {
const carbon1 = chain.calculateCarbon('diesel_truck', 50, 10);
const carbon2 = chain.calculateCarbon('diesel_truck', 100, 10);
expect(carbon2).toBe(carbon1 * 2);
});
it('should increase linearly with weight', () => {
const carbon1 = chain.calculateCarbon('diesel_truck', 100, 5);
const carbon2 = chain.calculateCarbon('diesel_truck', 100, 10);
expect(carbon2).toBe(carbon1 * 2);
});
it('should compare transport methods correctly', () => {
const distance = 100;
const weight = 10;
const walking = chain.calculateCarbon('walking', distance, weight);
const electric = chain.calculateCarbon('electric_vehicle', distance, weight);
const gasoline = chain.calculateCarbon('gasoline_vehicle', distance, weight);
const diesel = chain.calculateCarbon('diesel_truck', distance, weight);
const air = chain.calculateCarbon('air', distance, weight);
expect(walking).toBe(0);
expect(electric).toBeLessThan(gasoline);
expect(gasoline).toBeLessThan(diesel);
expect(diesel).toBeLessThan(air);
});
});
describe('Distance Calculation', () => {
it('should calculate zero distance for same location', () => {
const location: TransportLocation = {
latitude: 40.7128,
longitude: -74.006,
locationType: 'farm',
};
const distance = TransportChain.calculateDistance(location, location);
expect(distance).toBe(0);
});
it('should calculate correct distance for known locations', () => {
// New York to Los Angeles is approximately 3940 km
const newYork: TransportLocation = {
latitude: 40.7128,
longitude: -74.006,
locationType: 'warehouse',
};
const losAngeles: TransportLocation = {
latitude: 34.0522,
longitude: -118.2437,
locationType: 'warehouse',
};
const distance = TransportChain.calculateDistance(newYork, losAngeles);
expect(distance).toBeGreaterThan(3900);
expect(distance).toBeLessThan(4000);
});
it('should calculate short distances accurately', () => {
// Two points 1km apart (approximately)
const point1: TransportLocation = {
latitude: 40.7128,
longitude: -74.006,
locationType: 'farm',
};
const point2: TransportLocation = {
latitude: 40.7218, // ~1km north
longitude: -74.006,
locationType: 'farm',
};
const distance = TransportChain.calculateDistance(point1, point2);
expect(distance).toBeGreaterThan(0.9);
expect(distance).toBeLessThan(1.1);
});
it('should be symmetric', () => {
const pointA: TransportLocation = {
latitude: 40.7128,
longitude: -74.006,
locationType: 'farm',
};
const pointB: TransportLocation = {
latitude: 34.0522,
longitude: -118.2437,
locationType: 'warehouse',
};
const distanceAB = TransportChain.calculateDistance(pointA, pointB);
const distanceBA = TransportChain.calculateDistance(pointB, pointA);
expect(distanceAB).toBeCloseTo(distanceBA, 5);
});
it('should handle crossing the prime meridian', () => {
const london: TransportLocation = {
latitude: 51.5074,
longitude: -0.1278,
locationType: 'warehouse',
};
const paris: TransportLocation = {
latitude: 48.8566,
longitude: 2.3522,
locationType: 'warehouse',
};
const distance = TransportChain.calculateDistance(london, paris);
// London to Paris is approximately 344 km
expect(distance).toBeGreaterThan(330);
expect(distance).toBeLessThan(360);
});
it('should handle crossing the equator', () => {
const north: TransportLocation = {
latitude: 10,
longitude: 0,
locationType: 'farm',
};
const south: TransportLocation = {
latitude: -10,
longitude: 0,
locationType: 'farm',
};
const distance = TransportChain.calculateDistance(north, south);
// 20 degrees of latitude is approximately 2222 km
expect(distance).toBeGreaterThan(2200);
expect(distance).toBeLessThan(2250);
});
});
describe('Carbon Tracking in Events', () => {
it('should auto-calculate carbon if not provided', () => {
const event = {
id: 'test-carbon-auto',
timestamp: new Date().toISOString(),
eventType: 'seed_acquisition' as const,
fromLocation: { latitude: 40, longitude: -74, locationType: 'seed_bank' as const },
toLocation: { latitude: 41, longitude: -74, locationType: 'farm' as const },
distanceKm: 100,
durationMinutes: 120,
transportMethod: 'diesel_truck' as const,
carbonFootprintKg: 0, // Will be auto-calculated
senderId: 'sender',
receiverId: 'receiver',
status: 'verified' as const,
seedBatchId: 'batch-001',
sourceType: 'seed_bank' as const,
species: 'Test',
quantity: 100,
quantityUnit: 'seeds' as const,
generation: 1,
};
const block = chain.recordEvent(event);
expect(block.transportEvent.carbonFootprintKg).toBeGreaterThan(0);
});
it('should accumulate carbon across chain', () => {
const event1 = {
id: 'carbon-chain-1',
timestamp: new Date().toISOString(),
eventType: 'seed_acquisition' as const,
fromLocation: { latitude: 40, longitude: -74, locationType: 'seed_bank' as const },
toLocation: { latitude: 41, longitude: -74, locationType: 'farm' as const },
distanceKm: 100,
durationMinutes: 120,
transportMethod: 'diesel_truck' as const,
carbonFootprintKg: 0,
senderId: 'sender',
receiverId: 'receiver',
status: 'verified' as const,
seedBatchId: 'batch-001',
sourceType: 'seed_bank' as const,
species: 'Test',
quantity: 100,
quantityUnit: 'seeds' as const,
generation: 1,
};
const event2 = { ...event1, id: 'carbon-chain-2', distanceKm: 200 };
const block1 = chain.recordEvent(event1);
const block2 = chain.recordEvent(event2);
expect(block2.cumulativeCarbonKg).toBeGreaterThan(block1.cumulativeCarbonKg);
expect(block2.cumulativeCarbonKg).toBeCloseTo(
block1.cumulativeCarbonKg + block2.transportEvent.carbonFootprintKg,
5
);
});
});
describe('Environmental Impact Calculations', () => {
it('should calculate carbon savings vs conventional', () => {
const userId = 'eco-user';
const event = {
id: 'eco-event',
timestamp: new Date().toISOString(),
eventType: 'seed_acquisition' as const,
fromLocation: { latitude: 40, longitude: -74, locationType: 'seed_bank' as const },
toLocation: { latitude: 40.1, longitude: -74, locationType: 'farm' as const },
distanceKm: 10, // Very short local distance
durationMinutes: 20,
transportMethod: 'bicycle' as const, // Zero carbon
carbonFootprintKg: 0,
senderId: userId,
receiverId: userId,
status: 'verified' as const,
seedBatchId: 'batch-eco',
sourceType: 'seed_bank' as const,
species: 'Test',
quantity: 1000,
quantityUnit: 'grams' as const,
generation: 1,
};
chain.recordEvent(event);
const impact = chain.getEnvironmentalImpact(userId);
// Local, zero-carbon transport should save significantly vs conventional
expect(impact.comparisonToConventional.carbonSaved).toBeGreaterThan(0);
expect(impact.comparisonToConventional.milesSaved).toBeGreaterThan(0);
expect(impact.comparisonToConventional.percentageReduction).toBeGreaterThan(0);
});
it('should break down impact by transport method', () => {
const userId = 'breakdown-user';
// Record multiple events with different methods
const methods: TransportMethod[] = ['bicycle', 'electric_vehicle', 'diesel_truck'];
methods.forEach((method, i) => {
chain.recordEvent({
id: `breakdown-${i}`,
timestamp: new Date().toISOString(),
eventType: 'seed_acquisition' as const,
fromLocation: { latitude: 40, longitude: -74, locationType: 'seed_bank' as const },
toLocation: { latitude: 41, longitude: -74, locationType: 'farm' as const },
distanceKm: 50,
durationMinutes: 60,
transportMethod: method,
carbonFootprintKg: 0,
senderId: userId,
receiverId: userId,
status: 'verified' as const,
seedBatchId: `batch-${i}`,
sourceType: 'seed_bank' as const,
species: 'Test',
quantity: 100,
quantityUnit: 'seeds' as const,
generation: 1,
});
});
const impact = chain.getEnvironmentalImpact(userId);
expect(impact.breakdownByMethod['bicycle']).toBeDefined();
expect(impact.breakdownByMethod['electric_vehicle']).toBeDefined();
expect(impact.breakdownByMethod['diesel_truck']).toBeDefined();
// Bicycle should have zero carbon
expect(impact.breakdownByMethod['bicycle'].carbon).toBe(0);
// Diesel should have highest carbon
expect(impact.breakdownByMethod['diesel_truck'].carbon)
.toBeGreaterThan(impact.breakdownByMethod['electric_vehicle'].carbon);
});
});
});