- Add GitHub Actions CI workflow with lint, type-check, test, build, and e2e jobs - Configure Jest for unit and integration tests with coverage reporting - Create unit tests for BaseAgent, PlantLineageAgent, and AgentOrchestrator - Add blockchain PlantChain unit tests - Create API integration tests for plants endpoints - Configure Cypress for E2E testing with support files and custom commands - Add E2E tests for home, plant registration, transparency, and vertical farm pages - Set up Prettier for code formatting with configuration - Configure Husky pre-commit hooks with lint-staged - Add commitlint for conventional commit message enforcement - Update package.json with new scripts and dev dependencies This implements Agent 5 (Testing & CI/CD) from the deployment plan.
180 lines
5.1 KiB
TypeScript
180 lines
5.1 KiB
TypeScript
/**
|
|
* Plants API Tests
|
|
* Integration tests for plant-related API endpoints
|
|
*/
|
|
|
|
// Mock the blockchain manager
|
|
jest.mock('../../lib/blockchain/manager', () => ({
|
|
getBlockchain: jest.fn(() => ({
|
|
getChain: jest.fn(() => [
|
|
// Genesis block
|
|
{
|
|
index: 0,
|
|
timestamp: '2024-01-01T00:00:00Z',
|
|
plant: { id: 'genesis' },
|
|
previousHash: '0',
|
|
hash: 'genesis-hash',
|
|
nonce: 0,
|
|
},
|
|
// Test plants
|
|
{
|
|
index: 1,
|
|
timestamp: '2024-01-02T00:00:00Z',
|
|
plant: {
|
|
id: 'plant-1',
|
|
name: 'Cherry Tomato',
|
|
species: 'Tomato',
|
|
variety: 'Cherry',
|
|
generation: 1,
|
|
propagationType: 'original',
|
|
status: 'healthy',
|
|
location: { latitude: 40.7128, longitude: -74.006 },
|
|
},
|
|
previousHash: 'genesis-hash',
|
|
hash: 'hash-1',
|
|
nonce: 1,
|
|
},
|
|
{
|
|
index: 2,
|
|
timestamp: '2024-01-03T00:00:00Z',
|
|
plant: {
|
|
id: 'plant-2',
|
|
name: 'Sweet Basil',
|
|
species: 'Basil',
|
|
variety: 'Genovese',
|
|
generation: 1,
|
|
propagationType: 'seed',
|
|
parentPlantId: 'plant-1',
|
|
status: 'thriving',
|
|
location: { latitude: 40.7228, longitude: -74.016 },
|
|
},
|
|
previousHash: 'hash-1',
|
|
hash: 'hash-2',
|
|
nonce: 2,
|
|
},
|
|
]),
|
|
addPlant: jest.fn((plant) => ({
|
|
index: 3,
|
|
timestamp: new Date().toISOString(),
|
|
plant,
|
|
previousHash: 'hash-2',
|
|
hash: 'hash-3',
|
|
nonce: 3,
|
|
})),
|
|
findPlant: jest.fn((id) => {
|
|
if (id === 'plant-1') {
|
|
return {
|
|
index: 1,
|
|
plant: {
|
|
id: 'plant-1',
|
|
name: 'Cherry Tomato',
|
|
species: 'Tomato',
|
|
variety: 'Cherry',
|
|
generation: 1,
|
|
status: 'healthy',
|
|
},
|
|
};
|
|
}
|
|
return undefined;
|
|
}),
|
|
isValid: jest.fn(() => true),
|
|
})),
|
|
}));
|
|
|
|
describe('Plants API', () => {
|
|
describe('GET /api/plants', () => {
|
|
it('should return plant list', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
const chain = blockchain.getChain();
|
|
|
|
expect(chain.length).toBeGreaterThan(1);
|
|
expect(chain[1].plant.name).toBe('Cherry Tomato');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/plants/[id]', () => {
|
|
it('should return plant by ID', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
const plant = blockchain.findPlant('plant-1');
|
|
|
|
expect(plant).toBeDefined();
|
|
expect(plant.plant.name).toBe('Cherry Tomato');
|
|
});
|
|
|
|
it('should return undefined for non-existent plant', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
const plant = blockchain.findPlant('non-existent');
|
|
|
|
expect(plant).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('POST /api/plants/register', () => {
|
|
it('should register new plant', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
|
|
const newPlant = {
|
|
id: 'plant-3',
|
|
name: 'New Plant',
|
|
species: 'Test',
|
|
variety: 'Test',
|
|
generation: 1,
|
|
propagationType: 'seed',
|
|
status: 'healthy',
|
|
location: { latitude: 40.7, longitude: -74.0 },
|
|
};
|
|
|
|
const block = blockchain.addPlant(newPlant);
|
|
|
|
expect(block).toBeDefined();
|
|
expect(block.plant.name).toBe('New Plant');
|
|
expect(blockchain.addPlant).toHaveBeenCalledWith(newPlant);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/plants/network', () => {
|
|
it('should return network statistics', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
const chain = blockchain.getChain();
|
|
|
|
// Calculate network stats
|
|
const plants = chain.slice(1); // Skip genesis
|
|
const totalPlants = plants.length;
|
|
const speciesCounts: Record<string, number> = {};
|
|
|
|
plants.forEach((block: any) => {
|
|
const species = block.plant.species;
|
|
speciesCounts[species] = (speciesCounts[species] || 0) + 1;
|
|
});
|
|
|
|
expect(totalPlants).toBe(2);
|
|
expect(speciesCounts['Tomato']).toBe(1);
|
|
expect(speciesCounts['Basil']).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/plants/lineage/[id]', () => {
|
|
it('should return lineage for plant with parent', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
const chain = blockchain.getChain();
|
|
|
|
const plant2 = chain[2].plant;
|
|
expect(plant2.parentPlantId).toBe('plant-1');
|
|
});
|
|
});
|
|
|
|
describe('Blockchain Validation', () => {
|
|
it('should validate chain integrity', async () => {
|
|
const { getBlockchain } = require('../../lib/blockchain/manager');
|
|
const blockchain = getBlockchain();
|
|
|
|
expect(blockchain.isValid()).toBe(true);
|
|
});
|
|
});
|
|
});
|