import { describe, expect, it } from 'vitest' import { buildProvenanceGraph, hasAnySupplier, } from '@/components/provenance/provenance-graph' const ALL_KINDS = ['product', 'supplier', 'organization', 'municipality', 'department', 'country'] const singleRelation = [ { product: { id: 1, name: 'Panela regional por Kg', catalogue_images: ['http://localhost/media/panela.jpg'], }, suppliers: [ { supplier: { id: 5, name: 'Asociación Agropecuaria La Mesa' }, organization: { id: 3, name: 'Red de Economía Solidaria' }, municipality: { id: 7, name: 'La Mesa' }, department: { id: 2, name: 'Cundinamarca' }, country: { id: 1, name: 'Colombia' }, }, ], }, ] function twoSuppliers (overrides = {}) { return [ { product: { id: 1, name: 'Panela' }, suppliers: [ { supplier: { id: 5, name: 'Asociación A' }, organization: { id: 3, name: 'Red' }, municipality: { id: 7, name: 'La Mesa' }, department: { id: 2, name: 'Cundinamarca' }, country: { id: 1, name: 'Colombia' }, ...(overrides.first || {}), }, { supplier: { id: 6, name: 'Asociación B' }, organization: { id: 3, name: 'Red' }, municipality: { id: 8, name: 'San Antonio' }, department: { id: 3, name: 'Antioquia' }, country: { id: 1, name: 'Colombia' }, ...(overrides.second || {}), }, ], }, ] } function edgeOf (graph, from, to) { return graph.edges.find(edge => edge.from === from && edge.to === to) } describe('hasAnySupplier', () => { it('es true cuando algún producto tiene proveedores', () => { expect(hasAnySupplier(singleRelation)).toBe(true) }) it('es false cuando todos los productos están sin proveedores', () => { const empty = [{ product: { id: 1, name: 'Panela' }, suppliers: [] }] expect(hasAnySupplier(empty)).toBe(false) }) it('es false cuando no hay provenance', () => { expect(hasAnySupplier([])).toBe(false) }) }) describe('buildProvenanceGraph', () => { it('por defecto solo grafica productos y proveedores', () => { const graph = buildProvenanceGraph(singleRelation) expect(graph.nodes.map(n => n.kind)).toEqual(['product', 'supplier']) expect(graph.edges).toEqual([{ from: 'product:1', to: 'supplier:5', certain: true }]) }) it('con todos los niveles grafica la cadena completa y la organización', () => { const graph = buildProvenanceGraph(singleRelation, ALL_KINDS) expect(edgeOf(graph, 'product:1', 'supplier:5')).toEqual({ from: 'product:1', to: 'supplier:5', certain: true }) expect(edgeOf(graph, 'supplier:5', 'organization:3').certain).toBe(true) expect(edgeOf(graph, 'supplier:5', 'municipality:7')).toEqual({ from: 'supplier:5', to: 'municipality:7', certain: true }) expect(edgeOf(graph, 'municipality:7', 'department:2').certain).toBe(true) expect(edgeOf(graph, 'department:2', 'country:1').certain).toBe(true) }) it('con varios proveedores crea el punto de disyunción y la duda se propaga', () => { const graph = buildProvenanceGraph(twoSuppliers(), ALL_KINDS) expect(graph.nodes.map(n => n.id)).toContain('junction:1') expect(edgeOf(graph, 'product:1', 'junction:1')).toEqual({ from: 'product:1', to: 'junction:1', certain: true }) expect(edgeOf(graph, 'junction:1', 'supplier:5').certain).toBe(false) expect(edgeOf(graph, 'junction:1', 'supplier:6').certain).toBe(false) expect(edgeOf(graph, 'supplier:5', 'municipality:7').certain).toBe(false) expect(edgeOf(graph, 'municipality:7', 'department:2').certain).toBe(false) expect(edgeOf(graph, 'department:2', 'country:1').certain).toBe(true) expect(edgeOf(graph, 'supplier:5', 'organization:3').certain).toBe(true) }) it('omite los niveles no solicitados conectando el nivel previo con el siguiente', () => { const graph = buildProvenanceGraph(singleRelation, ['product', 'supplier', 'department']) expect(graph.nodes.map(n => n.kind)).toEqual(['product', 'supplier', 'department']) expect(edgeOf(graph, 'supplier:5', 'department:2')).toEqual({ from: 'supplier:5', to: 'department:2', certain: true }) expect(edgeOf(graph, 'supplier:5', 'municipality:7')).toBeUndefined() }) it('sin proveedores activos conecta el producto con el siguiente nivel', () => { const graph = buildProvenanceGraph(singleRelation, ['product', 'municipality', 'department']) expect(edgeOf(graph, 'product:1', 'municipality:7')).toEqual({ from: 'product:1', to: 'municipality:7', certain: true }) expect(edgeOf(graph, 'municipality:7', 'department:2').certain).toBe(true) }) it('sin productos activos los proveedores quedan como raíces sin junction', () => { const graph = buildProvenanceGraph(twoSuppliers(), ['supplier', 'municipality']) expect(graph.nodes.some(n => n.kind === 'product')).toBe(false) expect(graph.nodes.some(n => n.kind === 'junction')).toBe(false) expect(edgeOf(graph, 'supplier:5', 'municipality:7').certain).toBe(false) }) it('con país activo crea el nodo país aunque se omitan niveles intermedios', () => { const graph = buildProvenanceGraph(singleRelation, ['product', 'supplier', 'country']) expect(graph.nodes.map(n => n.id)).toContain('country:1') expect(edgeOf(graph, 'supplier:5', 'country:1').certain).toBe(true) }) it('no crea nodos de país si no se solicita', () => { const graph = buildProvenanceGraph(singleRelation, ['product', 'supplier', 'municipality']) expect(graph.nodes.some(n => n.kind === 'country')).toBe(false) }) it('no duplica aristas cuando varios productos comparten proveedor y municipio', () => { const data = [ { product: { id: 1, name: 'Panela' }, suppliers: [{ supplier: { id: 5, name: 'A' }, municipality: { id: 7, name: 'La Mesa' }, department: { id: 2, name: 'Cundinamarca' } }] }, { product: { id: 2, name: 'Arroz' }, suppliers: [{ supplier: { id: 5, name: 'A' }, municipality: { id: 7, name: 'La Mesa' }, department: { id: 2, name: 'Cundinamarca' } }] }, ] const graph = buildProvenanceGraph(data, ['product', 'supplier', 'municipality']) expect(graph.nodes.filter(n => n.id === 'municipality:7')).toHaveLength(1) expect(graph.edges.filter(e => e.from === 'supplier:5' && e.to === 'municipality:7')).toHaveLength(1) }) it('si el mismo par de nodos repite con distinta certeza, gana la duda', () => { const data = [ { product: { id: 1, name: 'Panela' }, suppliers: [{ supplier: { id: 5, name: 'A' }, organization: { id: 3, name: 'Red' } }] }, { product: { id: 2, name: 'Arroz' }, suppliers: [{ supplier: { id: 5, name: 'A' }, organization: { id: 3, name: 'Red' } }, { supplier: { id: 6, name: 'B' }, organization: { id: 4, name: 'Coop' } }] }, ] const graph = buildProvenanceGraph(data, ['product', 'supplier', 'organization']) expect(graph.edges.filter(e => e.from === 'supplier:5' && e.to === 'organization:3')).toHaveLength(1) expect(edgeOf(graph, 'supplier:5', 'organization:3').certain).toBe(false) }) it('incluye la imagen y el detalle de la entidad del producto', () => { const graph = buildProvenanceGraph(singleRelation) const product = graph.nodes.find(n => n.id === 'product:1') expect(product.image).toBe('http://localhost/media/panela.jpg') expect(product.entity.kind).toBe('product') }) it('devuelve un grafo vacío cuando no hay provenance', () => { const graph = buildProvenanceGraph([]) expect(graph.nodes).toEqual([]) expect(graph.edges).toEqual([]) }) })