/** * Seed-to-Seed Lifecycle Integration Tests * Tests complete lifecycle from seed acquisition to seed saving */ import { TransportChain, setTransportChain } from '../../lib/transport/tracker'; import { SeedAcquisitionEvent, PlantingEvent, GrowingTransportEvent, HarvestEvent, SeedSavingEvent, SeedSharingEvent, TransportLocation, } from '../../lib/transport/types'; describe('Seed-to-Seed Lifecycle', () => { let chain: TransportChain; beforeEach(() => { chain = new TransportChain(1); // Low difficulty for faster tests setTransportChain(chain); }); describe('Complete Lifecycle', () => { it('should track complete lifecycle from seed acquisition to seed saving', () => { const seedBatchId = 'lifecycle-batch-001'; const plantIds = ['plant-lifecycle-001', 'plant-lifecycle-002']; const newSeedBatchId = 'lifecycle-batch-002'; // Step 1: Seed Acquisition const seedAcquisition: SeedAcquisitionEvent = { id: 'lifecycle-seed-001', timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank', 'Heritage Seed Bank'), toLocation: createLocation('greenhouse', 'Local Grower Greenhouse'), distanceKm: 50, durationMinutes: 60, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'seed-bank-heritage', receiverId: 'grower-local-001', status: 'verified', seedBatchId, sourceType: 'seed_bank', species: 'Solanum lycopersicum', variety: 'Brandywine', quantity: 50, quantityUnit: 'seeds', generation: 3, germinationRate: 92, certifications: ['heirloom', 'organic'], }; const block1 = chain.recordEvent(seedAcquisition); expect(block1.transportEvent.eventType).toBe('seed_acquisition'); // Step 2: Planting const planting: PlantingEvent = { id: 'lifecycle-planting-001', timestamp: new Date(Date.now() + 1000).toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse', 'Local Grower Greenhouse'), toLocation: createLocation('greenhouse', 'Local Grower Greenhouse'), distanceKm: 0, durationMinutes: 30, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-local-001', receiverId: 'grower-local-001', status: 'verified', seedBatchId, plantIds, plantingMethod: 'indoor_start', quantityPlanted: 2, growingEnvironment: 'greenhouse', expectedHarvestDate: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(), }; const block2 = chain.recordEvent(planting); expect(block2.transportEvent.eventType).toBe('planting'); // Step 3: Growing Transport (transplant to outdoor garden) const transplant: GrowingTransportEvent = { id: 'lifecycle-transplant-001', timestamp: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days later eventType: 'growing_transport', fromLocation: createLocation('greenhouse', 'Local Grower Greenhouse'), toLocation: createLocation('farm', 'Local Community Garden'), distanceKm: 2, durationMinutes: 20, transportMethod: 'bicycle', carbonFootprintKg: 0, senderId: 'grower-local-001', receiverId: 'grower-local-001', status: 'verified', plantIds, reason: 'transplant', plantStage: 'vegetative', handlingMethod: 'potted', rootDisturbance: 'minimal', acclimatizationRequired: true, acclimatizationDays: 7, }; const block3 = chain.recordEvent(transplant); expect(block3.transportEvent.eventType).toBe('growing_transport'); // Step 4: Harvest const harvest: HarvestEvent = { id: 'lifecycle-harvest-001', timestamp: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(), // 90 days after planting eventType: 'harvest', fromLocation: createLocation('farm', 'Local Community Garden'), toLocation: createLocation('warehouse', 'Local Co-op Distribution'), distanceKm: 5, durationMinutes: 15, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'grower-local-001', receiverId: 'coop-distribution', status: 'verified', plantIds, harvestBatchId: 'harvest-lifecycle-001', harvestType: 'full', produceType: 'Brandywine Tomatoes', grossWeight: 8, netWeight: 7.5, weightUnit: 'kg', qualityGrade: 'A', packagingType: 'sustainable_crates', temperatureRequired: { min: 12, max: 18, optimal: 15, unit: 'celsius' }, shelfLifeHours: 168, seedsSaved: true, seedBatchIdCreated: newSeedBatchId, }; const block4 = chain.recordEvent(harvest); expect(block4.transportEvent.eventType).toBe('harvest'); // Step 5: Seed Saving const seedSaving: SeedSavingEvent = { id: 'lifecycle-seed-saving-001', timestamp: new Date(Date.now() + 95 * 24 * 60 * 60 * 1000).toISOString(), eventType: 'seed_saving', fromLocation: createLocation('farm', 'Local Community Garden'), toLocation: createLocation('seed_bank', 'Grower Home Seed Storage'), distanceKm: 1, durationMinutes: 10, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-local-001', receiverId: 'grower-local-001', status: 'verified', parentPlantIds: plantIds, newSeedBatchId, collectionMethod: 'fermentation', seedCount: 200, germinationRate: 88, storageConditions: { temperature: 10, humidity: 30, lightExposure: 'dark', containerType: 'jar', desiccant: true, estimatedViability: 5, }, storageLocationId: 'grower-home-storage', newGenerationNumber: 4, geneticNotes: 'Selected from best performing plants', availableForSharing: true, sharingTerms: 'trade', }; const block5 = chain.recordEvent(seedSaving); expect(block5.transportEvent.eventType).toBe('seed_saving'); // Verify complete journey const journey1 = chain.getPlantJourney(plantIds[0]); expect(journey1).not.toBeNull(); expect(journey1?.events.length).toBe(4); // planting, transplant, harvest, seed_saving expect(journey1?.currentStage).toBe('seed_saving'); expect(journey1?.descendantSeedBatches).toContain(newSeedBatchId); // Verify chain integrity expect(chain.isChainValid()).toBe(true); // Verify carbon tracking expect(block5.cumulativeCarbonKg).toBeGreaterThanOrEqual(0); expect(block5.cumulativeFoodMiles).toBe( seedAcquisition.distanceKm + planting.distanceKm + transplant.distanceKm + harvest.distanceKm + seedSaving.distanceKm ); }); it('should track multiple generations', () => { // Generation 1 seeds const gen1Batch = 'gen1-batch'; const gen1Plants = ['gen1-plant-001']; const gen2Batch = 'gen2-batch'; const gen2Plants = ['gen2-plant-001']; const gen3Batch = 'gen3-batch'; // Gen 1: Acquire, Plant, Harvest with seed saving chain.recordEvent(createSeedAcquisition(gen1Batch, 1)); chain.recordEvent(createPlanting(gen1Batch, gen1Plants)); chain.recordEvent(createHarvest(gen1Plants, gen2Batch)); chain.recordEvent(createSeedSaving(gen1Plants, gen2Batch, 2)); // Gen 2: Plant saved seeds, Harvest with seed saving chain.recordEvent(createSeedAcquisition(gen2Batch, 2, gen1Plants)); chain.recordEvent(createPlanting(gen2Batch, gen2Plants)); chain.recordEvent(createHarvest(gen2Plants, gen3Batch)); chain.recordEvent(createSeedSaving(gen2Plants, gen3Batch, 3)); // Verify chain expect(chain.isChainValid()).toBe(true); expect(chain.chain.length).toBe(9); // 1 genesis + 8 events // Verify lineage const journey = chain.getPlantJourney('gen2-plant-001'); expect(journey?.generation).toBe(2); }); }); describe('Seed Sharing', () => { it('should track seed sharing between growers', () => { const originalBatch = 'original-batch'; const sharedQuantity = 25; // Save seeds const seedSaving: SeedSavingEvent = { id: 'share-seed-saving', timestamp: new Date().toISOString(), eventType: 'seed_saving', fromLocation: createLocation('farm', 'Grower A Farm'), toLocation: createLocation('seed_bank', 'Grower A Storage'), distanceKm: 0, durationMinutes: 30, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-a', receiverId: 'grower-a', status: 'verified', parentPlantIds: ['parent-001'], newSeedBatchId: originalBatch, collectionMethod: 'dry_seed', seedCount: 100, storageConditions: { temperature: 10, humidity: 30, lightExposure: 'dark', containerType: 'envelope', desiccant: true, estimatedViability: 4, }, storageLocationId: 'grower-a-storage', newGenerationNumber: 2, availableForSharing: true, sharingTerms: 'trade', }; chain.recordEvent(seedSaving); // Share seeds const seedSharing: SeedSharingEvent = { id: 'share-event-001', timestamp: new Date(Date.now() + 1000).toISOString(), eventType: 'seed_sharing', fromLocation: createLocation('seed_bank', 'Grower A Storage'), toLocation: createLocation('greenhouse', 'Grower B Greenhouse'), distanceKm: 10, durationMinutes: 20, transportMethod: 'bicycle', carbonFootprintKg: 0, senderId: 'grower-a', receiverId: 'grower-b', status: 'verified', seedBatchId: originalBatch, quantityShared: sharedQuantity, quantityUnit: 'seeds', sharingType: 'trade', tradeDetails: 'Traded for basil seeds', recipientAgreement: true, growingCommitment: 'Will grow and save seeds', reportBackRequired: true, }; const block = chain.recordEvent(seedSharing); expect(block.transportEvent.eventType).toBe('seed_sharing'); expect((block.transportEvent as SeedSharingEvent).quantityShared).toBe(sharedQuantity); }); }); describe('Environmental Impact Across Lifecycle', () => { it('should calculate cumulative environmental impact', () => { const userId = 'eco-grower'; // Record full lifecycle with the same user const events = [ { distanceKm: 50, method: 'electric_vehicle' as const }, { distanceKm: 0, method: 'walking' as const }, { distanceKm: 5, method: 'bicycle' as const }, { distanceKm: 10, method: 'electric_truck' as const }, { distanceKm: 1, method: 'walking' as const }, ]; let totalDistance = 0; events.forEach((event, i) => { chain.recordEvent({ id: `eco-event-${i}`, timestamp: new Date(Date.now() + i * 1000).toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('farm', 'Location A'), toLocation: createLocation('farm', 'Location B'), distanceKm: event.distanceKm, durationMinutes: 30, transportMethod: event.method, carbonFootprintKg: 0, senderId: userId, receiverId: userId, status: 'verified', seedBatchId: `batch-${i}`, sourceType: 'previous_harvest', species: 'Test', quantity: 10, quantityUnit: 'seeds', generation: 1, } as SeedAcquisitionEvent); totalDistance += event.distanceKm; }); const impact = chain.getEnvironmentalImpact(userId); expect(impact.totalFoodMiles).toBe(totalDistance); expect(impact.comparisonToConventional.milesSaved).toBeGreaterThan(0); }); }); describe('QR Code Traceability', () => { it('should generate traceable QR codes at each stage', () => { const plantId = 'qr-trace-plant'; const batchId = 'qr-trace-batch'; // Record seed acquisition chain.recordEvent({ id: 'qr-seed', timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank', 'Bank'), toLocation: createLocation('greenhouse', 'Greenhouse'), distanceKm: 10, durationMinutes: 20, transportMethod: 'bicycle', carbonFootprintKg: 0, senderId: 'seller', receiverId: 'buyer', status: 'verified', seedBatchId: batchId, sourceType: 'seed_bank', species: 'Test', quantity: 10, quantityUnit: 'seeds', generation: 1, } as SeedAcquisitionEvent); // Record planting chain.recordEvent({ id: 'qr-planting', timestamp: new Date(Date.now() + 1000).toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse', 'Greenhouse'), toLocation: createLocation('greenhouse', 'Greenhouse'), distanceKm: 0, durationMinutes: 10, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'buyer', receiverId: 'buyer', status: 'verified', seedBatchId: batchId, plantIds: [plantId], plantingMethod: 'indoor_start', quantityPlanted: 1, growingEnvironment: 'greenhouse', } as PlantingEvent); // Generate QR for plant const plantQR = chain.generateQRData(plantId, undefined); expect(plantQR.plantId).toBe(plantId); expect(plantQR.lastEventType).toBe('planting'); // Generate QR for batch const batchQR = chain.generateQRData(undefined, batchId); expect(batchQR.batchId).toBe(batchId); // Both should have valid verification codes expect(plantQR.verificationCode).toMatch(/^[A-F0-9]{8}$/); expect(batchQR.verificationCode).toMatch(/^[A-F0-9]{8}$/); }); }); }); // Helper functions function createLocation(type: string, name: string): TransportLocation { return { latitude: 40.7 + Math.random() * 0.1, longitude: -74.0 + Math.random() * 0.1, locationType: type as any, facilityName: name, }; } function createSeedAcquisition( batchId: string, generation: number, parentPlantIds?: string[] ): SeedAcquisitionEvent { return { id: `seed-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank', 'Bank'), toLocation: createLocation('greenhouse', 'Greenhouse'), distanceKm: 10, durationMinutes: 20, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'sender', receiverId: 'receiver', status: 'verified', seedBatchId: batchId, sourceType: parentPlantIds ? 'previous_harvest' : 'seed_bank', species: 'Test Species', quantity: 10, quantityUnit: 'seeds', generation, parentPlantIds, }; } function createPlanting(batchId: string, plantIds: string[]): PlantingEvent { return { id: `planting-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, timestamp: new Date(Date.now() + 100).toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse', 'Greenhouse'), toLocation: createLocation('greenhouse', 'Greenhouse'), distanceKm: 0, durationMinutes: 10, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower', receiverId: 'grower', status: 'verified', seedBatchId: batchId, plantIds, plantingMethod: 'indoor_start', quantityPlanted: plantIds.length, growingEnvironment: 'greenhouse', }; } function createHarvest(plantIds: string[], newSeedBatchId: string): HarvestEvent { return { id: `harvest-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, timestamp: new Date(Date.now() + 200).toISOString(), eventType: 'harvest', fromLocation: createLocation('farm', 'Farm'), toLocation: createLocation('warehouse', 'Warehouse'), distanceKm: 5, durationMinutes: 20, transportMethod: 'electric_truck', carbonFootprintKg: 0, senderId: 'grower', receiverId: 'distributor', status: 'verified', plantIds, harvestBatchId: `harvest-batch-${Date.now()}`, harvestType: 'full', produceType: 'Test Produce', grossWeight: 5, netWeight: 4.5, weightUnit: 'kg', packagingType: 'crates', temperatureRequired: { min: 10, max: 20, optimal: 15, unit: 'celsius' }, shelfLifeHours: 168, seedsSaved: true, seedBatchIdCreated: newSeedBatchId, }; } function createSeedSaving( parentPlantIds: string[], newSeedBatchId: string, generation: number ): SeedSavingEvent { return { id: `save-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`, timestamp: new Date(Date.now() + 300).toISOString(), eventType: 'seed_saving', fromLocation: createLocation('farm', 'Farm'), toLocation: createLocation('seed_bank', 'Storage'), distanceKm: 0, durationMinutes: 30, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower', receiverId: 'grower', status: 'verified', parentPlantIds, newSeedBatchId, collectionMethod: 'dry_seed', seedCount: 50, storageConditions: { temperature: 10, humidity: 30, lightExposure: 'dark', containerType: 'envelope', desiccant: true, estimatedViability: 4, }, storageLocationId: 'grower-storage', newGenerationNumber: generation, availableForSharing: true, }; }