From a23596419a84adfa1f08e97a76f50b4a566cd9bb Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Mon, 30 Mar 2026 14:13:14 +0200 Subject: [PATCH 1/3] feat: add tests Signed-off-by: Umberto Sgueglia --- pnpm-lock.yaml | 828 +++++++++++++++++- services/libs/data-access-layer/package.json | 6 +- .../__tests__/affiliations.test.ts | 603 +++++++++++++ .../src/affiliations/index.ts | 6 +- .../libs/data-access-layer/vitest.config.ts | 13 + 5 files changed, 1447 insertions(+), 9 deletions(-) create mode 100644 services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts create mode 100644 services/libs/data-access-layer/vitest.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc406b82ae..2ad2066130 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2103,6 +2103,9 @@ importers: typescript: specifier: ^5.6.3 version: 5.6.3 + vitest: + specifier: ^2.1.9 + version: 2.1.9(@types/node@18.19.31)(terser@5.43.1) services/libs/database: dependencies: @@ -3706,138 +3709,276 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.19.12': resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.19.12': resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.19.12': resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.12': resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.19.12': resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.19.12': resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.19.12': resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.19.12': resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.19.12': resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.19.12': resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.19.12': resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.19.12': resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.19.12': resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-x64@0.19.12': resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.19.12': resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.19.12': resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3966,6 +4107,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -4286,6 +4430,131 @@ packages: peerDependencies: '@redis/client': ^1.0.0 + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + '@sapphire/async-queue@1.5.2': resolution: {integrity: sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -5186,6 +5455,35 @@ packages: resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} hasBin: true + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vladfrangu/async_event_emitter@2.2.4': resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -5469,6 +5767,10 @@ packages: resolution: {integrity: sha512-5lNGRB5g5i2bGIzb+J1QQE1iKU/WEMVBReFIc5pPDWjcPj23otPL0eI6PB2v7QPi0qU6Mhym5D3y0ZiSIOf3GA==} engines: {node: '>=10.0.0'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} @@ -5704,6 +6006,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + cacheable-request@6.1.0: resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} engines: {node: '>=8'} @@ -5749,6 +6055,10 @@ packages: resolution: {integrity: sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==} hasBin: true + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -5768,6 +6078,10 @@ packages: charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6169,6 +6483,10 @@ packages: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -6502,6 +6820,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -6661,6 +6984,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -6708,6 +7034,10 @@ packages: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + express-oauth2-jwt-bearer@1.7.4: resolution: {integrity: sha512-teO/eyvU8OkJXiP4cRuoJMrp31nNvjnL47MIkso0D/21AqUGv1O+VEiLisrDA8xjkaCBTufYnV1zepCOCLK4vg==} engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0 || ^20.2.0 || ^22.1.0 || ^24.0.0} @@ -7057,11 +7387,11 @@ packages: glob@6.0.4: resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} @@ -7966,6 +8296,9 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowercase-keys@1.0.1: resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} engines: {node: '>=0.10.0'} @@ -8012,6 +8345,9 @@ packages: magic-bytes.js@1.10.0: resolution: {integrity: sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -8200,6 +8536,11 @@ packages: nan@2.19.0: resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -8730,6 +9071,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + pause@0.0.1: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} @@ -8830,6 +9178,10 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -9159,6 +9511,11 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} @@ -9383,6 +9740,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -9445,6 +9805,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-loader@4.0.2: resolution: {integrity: sha512-oYwAqCuL0OZhBoSgmdrLa7mv9MjommVMiQIWgcztf+eS4+8BfcUee6nenFnDhKOhzAVnk5gpZdfnz1iiBv+5sg==} engines: {node: '>= 14.15.0'} @@ -9492,6 +9856,9 @@ packages: stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -9500,6 +9867,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stream-events@1.0.5: resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} @@ -9728,9 +10098,24 @@ packages: timers-ext@0.1.7: resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tldts-core@6.1.18: resolution: {integrity: sha512-e4wx32F/7dMBSZyKAx825Yte3U0PQtZZ0bkWxYQiwLteRVnQ5zM40fEbi0IyNtwQssgJAk3GCr7Q+w39hX0VKA==} @@ -10060,6 +10445,67 @@ packages: verify-github-webhook@1.0.1: resolution: {integrity: sha512-c5Kv/wzbPBii5s2FyZssgarhMCaHCQAXexqKl2JX/BbCCbZ8oRICS+x9E82pdCNnV9az2sd9SOb+MUtdPNHyvA==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vm2@3.9.19: resolution: {integrity: sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==} engines: {node: '>=6.0'} @@ -10121,6 +10567,11 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -12802,72 +13253,141 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/android-arm64@0.19.12': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm@0.19.12': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-x64@0.19.12': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.19.12': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.19.12': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.19.12': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.19.12': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.19.12': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm@0.19.12': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-ia32@0.19.12': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-loong64@0.19.12': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.19.12': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.19.12': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.19.12': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-s390x@0.19.12': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-x64@0.19.12': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.19.12': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.19.12': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.19.12': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.19.12': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-ia32@0.19.12': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-x64@0.19.12': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -13031,7 +13551,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/gen-mapping@0.3.8': @@ -13053,10 +13573,12 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping@0.3.9': dependencies: @@ -13511,6 +14033,81 @@ snapshots: dependencies: '@redis/client': 1.5.14 + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + '@sapphire/async-queue@1.5.2': {} '@sapphire/shapeshift@3.9.7': @@ -14792,6 +15389,46 @@ snapshots: '@vercel/ncc@0.38.1': {} + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + '@vladfrangu/async_event_emitter@2.2.4': {} '@webassemblyjs/ast@1.14.1': @@ -15116,6 +15753,8 @@ snapshots: assert-options@0.8.1: {} + assertion-error@2.0.1: {} + async-limiter@1.0.1: {} async-retry@1.3.3: @@ -15454,6 +16093,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + cacheable-request@6.1.0: dependencies: clone-response: 1.0.3 @@ -15498,6 +16139,14 @@ snapshots: cargo-cp-artifact@0.1.9: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -15518,6 +16167,8 @@ snapshots: charenc@0.0.2: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -15952,6 +16603,8 @@ snapshots: dependencies: mimic-response: 1.0.1 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -16379,6 +17032,32 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + escalade@3.1.2: {} escalade@3.2.0: {} @@ -16563,6 +17242,10 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} etag@1.8.1: {} @@ -16612,6 +17295,8 @@ snapshots: dependencies: homedir-polyfill: 1.0.3 + expect-type@1.3.0: {} + express-oauth2-jwt-bearer@1.7.4: dependencies: jose: 4.15.5 @@ -18027,6 +18712,8 @@ snapshots: long@5.3.2: {} + loupe@3.2.1: {} + lowercase-keys@1.0.1: {} lowercase-keys@2.0.0: {} @@ -18068,6 +18755,10 @@ snapshots: magic-bytes.js@1.10.0: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-dir@3.1.0: dependencies: semver: 6.3.1 @@ -18231,6 +18922,8 @@ snapshots: nan@2.19.0: {} + nanoid@3.3.11: {} + nanoid@3.3.7: {} natural-compare-lite@1.4.0: {} @@ -18821,6 +19514,10 @@ snapshots: path-type@4.0.0: {} + pathe@1.1.2: {} + + pathval@2.0.1: {} + pause@0.0.1: {} peberminta@0.9.0: {} @@ -18913,6 +19610,12 @@ snapshots: picocolors: 1.0.0 source-map-js: 1.2.0 + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postgres-array@2.0.0: {} postgres-bytea@1.0.0: {} @@ -19283,6 +19986,37 @@ snapshots: dependencies: glob: 10.3.12 + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + run-applescript@7.1.0: {} run-parallel@1.2.0: @@ -19584,6 +20318,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -19696,6 +20432,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map-loader@4.0.2(webpack@5.99.9(@swc/core@1.4.17)): dependencies: iconv-lite: 0.6.3 @@ -19735,10 +20473,14 @@ snapshots: stack-trace@0.0.10: {} + stackback@0.0.2: {} + statuses@1.5.0: {} statuses@2.0.1: {} + std-env@3.10.0: {} + stream-events@1.0.5: dependencies: stubs: 3.0.0 @@ -20013,8 +20755,16 @@ snapshots: es5-ext: 0.10.64 next-tick: 1.1.0 + tinybench@2.9.0: {} + tinyexec@0.3.2: {} + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + tldts-core@6.1.18: {} tldts@6.1.18: @@ -20246,7 +20996,7 @@ snapshots: dependencies: browserslist: 4.23.0 escalade: 3.1.2 - picocolors: 1.0.0 + picocolors: 1.1.1 update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: @@ -20335,6 +21085,69 @@ snapshots: verify-github-webhook@1.0.1: {} + vite-node@2.1.9(@types/node@18.19.31)(terser@5.43.1): + dependencies: + cac: 6.7.14 + debug: 4.4.0(supports-color@5.5.0) + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@18.19.31)(terser@5.43.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.8 + rollup: 4.60.1 + optionalDependencies: + '@types/node': 18.19.31 + fsevents: 2.3.3 + terser: 5.43.1 + + vitest@2.1.9(@types/node@18.19.31)(terser@5.43.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.0(supports-color@5.5.0) + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) + vite-node: 2.1.9(@types/node@18.19.31)(terser@5.43.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 18.19.31 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vm2@3.9.19: dependencies: acorn: 8.11.3 @@ -20421,6 +21234,11 @@ snapshots: dependencies: isexe: 3.1.1 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 diff --git a/services/libs/data-access-layer/package.json b/services/libs/data-access-layer/package.json index e3a4086607..f62e8d5faa 100644 --- a/services/libs/data-access-layer/package.json +++ b/services/libs/data-access-layer/package.json @@ -2,6 +2,9 @@ "name": "@crowd/data-access-layer", "main": "src/index.ts", "scripts": { + "test": "vitest run", + "test:affiliations": "vitest run src/affiliations", + "test:affiliations:watch": "vitest src/affiliations", "lint": "npx eslint --ext .ts src --max-warnings=0", "format": "npx prettier --write \"src/**/*.ts\"", "format-check": "npx prettier --check .", @@ -34,6 +37,7 @@ "devDependencies": { "@types/node": "^18.16.3", "sequelize": "6.37.8", - "typescript": "^5.6.3" + "typescript": "^5.6.3", + "vitest": "^2.1.9" } } diff --git a/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts b/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts new file mode 100644 index 0000000000..08c7861585 --- /dev/null +++ b/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts @@ -0,0 +1,603 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +// Mocks are hoisted before imports — intercept transitive dependencies that +// require a live database or external services. +vi.mock('@crowd/database', () => ({})) +vi.mock('@crowd/logging', () => ({ + getServiceLogger: () => ({ info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn() }), + getServiceChildLogger: () => ({ + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }), + LoggerBase: class {}, +})) +vi.mock('@crowd/redis', () => ({})) + +// Provide the blacklist constant used by findWorkExperiencesBulk without loading +// the full members/base module (which has heavy DB dependencies). +vi.mock('../../members/base', () => ({ + BLACKLISTED_MEMBER_TITLES: ['investor', 'mentor', 'board member'], +})) + +import type { QueryExecutor } from '../../queryExecutor' +import { + buildTimeline, + resolveAffiliationsByMemberIds, + selectPrimaryWorkExperience, +} from '../index' +import type { IWorkExperienceResolution } from '../index' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +// Mirror of the private startOfDay() in affiliations/index.ts. +// Kept in sync manually: sets a date to UTC midnight so boundary comparisons +// in tests produce the same values as the production code. +function startOfDay(date: string): Date { + const d = new Date(date) + d.setUTCHours(0, 0, 0, 0) + return d +} + +function makeRow(overrides: Partial = {}): IWorkExperienceResolution { + return { + id: 'row-1', + memberId: 'member-1', + organizationId: 'org-1', + organizationName: 'Test Org', + title: null, + dateStart: null, + dateEnd: null, + createdAt: '2020-01-01T00:00:00.000Z', + isPrimaryWorkExperience: false, + memberCount: 1, + segmentId: null, + ...overrides, + } +} + +// --------------------------------------------------------------------------- +// selectPrimaryWorkExperience +// --------------------------------------------------------------------------- + +describe('selectPrimaryWorkExperience', () => { + it('returns the single org when the array has exactly one element', () => { + const org = makeRow({ organizationName: 'Solo' }) + expect(selectPrimaryWorkExperience([org])).toBe(org) + }) + + it('picks the manual affiliation (segmentId != null) over work experiences', () => { + const workExp = makeRow({ organizationId: 'work', organizationName: 'Work Inc', segmentId: null }) + const manual = makeRow({ organizationId: 'manual', organizationName: 'Manual Org', segmentId: 'seg-1' }) + const result = selectPrimaryWorkExperience([workExp, manual]) + expect(result.organizationName).toBe('Manual Org') + }) + + it('picks the manual with the longest date range when there are multiple manual affiliations', () => { + const shortManual = makeRow({ + organizationId: 'm1', + organizationName: 'Short', + segmentId: 'seg-1', + dateStart: '2020-01-01', + dateEnd: '2020-06-30', + }) + const longManual = makeRow({ + organizationId: 'm2', + organizationName: 'Long', + segmentId: 'seg-2', + dateStart: '2018-01-01', + dateEnd: '2022-12-31', + }) + const result = selectPrimaryWorkExperience([shortManual, longManual]) + expect(result.organizationName).toBe('Long') + }) + + it('picks the isPrimaryWorkExperience=true row when no manual affiliations exist', () => { + const plain = makeRow({ organizationId: 'plain', organizationName: 'Plain', dateStart: '2018-01-01' }) + const primary = makeRow({ + organizationId: 'primary', + organizationName: 'Primary', + isPrimaryWorkExperience: true, + dateStart: '2020-01-01', + }) + const result = selectPrimaryWorkExperience([plain, primary]) + expect(result.organizationName).toBe('Primary') + }) + + it('prefers the primary row with a dateStart over one without', () => { + const noDate = makeRow({ + organizationId: 'nodates', + organizationName: 'NoDates', + isPrimaryWorkExperience: true, + dateStart: null, + }) + const withDate = makeRow({ + organizationId: 'wdates', + organizationName: 'WithDates', + isPrimaryWorkExperience: true, + dateStart: '2020-01-01', + }) + const result = selectPrimaryWorkExperience([noDate, withDate]) + expect(result.organizationName).toBe('WithDates') + }) + + it('picks the single dated org when no primary and only one has a dateStart', () => { + const undated = makeRow({ organizationId: 'u', organizationName: 'Undated', dateStart: null }) + const dated = makeRow({ organizationId: 'd', organizationName: 'Dated', dateStart: '2020-01-01' }) + const result = selectPrimaryWorkExperience([undated, dated]) + expect(result.organizationName).toBe('Dated') + }) + + it('picks the org with more members when no primary and multiple have dates', () => { + const small = makeRow({ + organizationId: 'small', + organizationName: 'Small', + dateStart: '2020-01-01', + memberCount: 10, + }) + const large = makeRow({ + organizationId: 'large', + organizationName: 'Large', + dateStart: '2019-01-01', + memberCount: 100, + }) + const result = selectPrimaryWorkExperience([small, large]) + expect(result.organizationName).toBe('Large') + }) + + it('falls through to longest date range when memberCounts are tied', () => { + const shortRange = makeRow({ + organizationId: 'short', + organizationName: 'ShortRange', + dateStart: '2020-01-01', + dateEnd: '2021-12-31', + memberCount: 10, + }) + const longRange = makeRow({ + organizationId: 'long', + organizationName: 'LongRange', + dateStart: '2018-01-01', + dateEnd: '2022-12-31', + memberCount: 10, + }) + const result = selectPrimaryWorkExperience([shortRange, longRange]) + expect(result.organizationName).toBe('LongRange') + }) +}) + +// --------------------------------------------------------------------------- +// buildTimeline +// --------------------------------------------------------------------------- + +describe('buildTimeline', () => { + it('produces a single ongoing period for one undated-end org', () => { + const org = makeRow({ organizationName: 'Acme', dateStart: '2020-01-01', dateEnd: null }) + const boundaries = [startOfDay('2020-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([org], null, boundaries) + + expect(result).toEqual([ + { + organization: 'Acme', + startDate: startOfDay('2020-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('produces a single closed period for an org with both start and end dates', () => { + const org = makeRow({ organizationName: 'Acme', dateStart: '2020-01-01', dateEnd: '2022-12-31' }) + // afterEnd boundary: day after dateEnd + const boundaries = [startOfDay('2020-01-01'), startOfDay('2023-01-01')] + + const result = buildTimeline([org], null, boundaries) + + expect(result).toEqual([ + { + organization: 'Acme', + startDate: startOfDay('2020-01-01').toISOString(), + endDate: startOfDay('2022-12-31').toISOString(), + }, + ]) + }) + + it('produces two sequential periods when orgs share a boundary with no gap', () => { + const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2020-12-31' }) + const org2 = makeRow({ organizationId: 'o2', organizationName: 'Org2', dateStart: '2021-01-01', dateEnd: null }) + // 2021-01-01 is both the afterEnd of org1 and the dateStart of org2 + const boundaries = [startOfDay('2018-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([org1, org2], null, boundaries) + + expect(result).toEqual([ + { + organization: 'Org1', + startDate: startOfDay('2018-01-01').toISOString(), + endDate: startOfDay('2020-12-31').toISOString(), + }, + { + organization: 'Org2', + startDate: startOfDay('2021-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('fills a gap between two orgs with the fallback org', () => { + const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2019-12-31' }) + const org2 = makeRow({ organizationId: 'o2', organizationName: 'Org2', dateStart: '2021-01-01', dateEnd: null }) + const fallback = makeRow({ organizationId: 'fb', organizationName: 'Fallback' }) + const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([org1, org2], fallback, boundaries) + + expect(result).toEqual([ + { + organization: 'Org1', + startDate: startOfDay('2018-01-01').toISOString(), + endDate: startOfDay('2019-12-31').toISOString(), + }, + { + organization: 'Fallback', + startDate: startOfDay('2020-01-01').toISOString(), + endDate: startOfDay('2020-12-31').toISOString(), + }, + { + organization: 'Org2', + startDate: startOfDay('2021-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('leaves a gap unfilled when there is no fallback org', () => { + const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2019-12-31' }) + const org2 = makeRow({ organizationId: 'o2', organizationName: 'Org2', dateStart: '2021-01-01', dateEnd: null }) + const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([org1, org2], null, boundaries) + + expect(result).toEqual([ + { + organization: 'Org1', + startDate: startOfDay('2018-01-01').toISOString(), + endDate: startOfDay('2019-12-31').toISOString(), + }, + { + organization: 'Org2', + startDate: startOfDay('2021-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('extends the fallback org to cover a trailing gap after the last dated org ends', () => { + const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2019-12-31' }) + const fallback = makeRow({ organizationId: 'fb', organizationName: 'Fallback' }) + const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([org1], fallback, boundaries) + + expect(result).toEqual([ + { + organization: 'Org1', + startDate: startOfDay('2018-01-01').toISOString(), + endDate: startOfDay('2019-12-31').toISOString(), + }, + { + organization: 'Fallback', + startDate: startOfDay('2020-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('switches winner at the boundary where a higher-member-count org becomes active', () => { + const low = makeRow({ + organizationId: 'low', + organizationName: 'LowMember', + dateStart: '2018-01-01', + dateEnd: '2021-12-31', + memberCount: 10, + }) + const high = makeRow({ + organizationId: 'high', + organizationName: 'HighMember', + dateStart: '2020-01-01', + dateEnd: null, + memberCount: 100, + }) + const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2022-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([low, high], null, boundaries) + + expect(result).toEqual([ + { + organization: 'LowMember', + startDate: startOfDay('2018-01-01').toISOString(), + endDate: startOfDay('2019-12-31').toISOString(), + }, + { + organization: 'HighMember', + startDate: startOfDay('2020-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('undated org in allRows competes at every boundary and takes over after the dated org ends', () => { + const undated = makeRow({ organizationId: 'undated', organizationName: 'Undated', dateStart: null, dateEnd: null }) + const dated = makeRow({ organizationId: 'dated', organizationName: 'Dated', dateStart: '2020-01-01', dateEnd: '2020-12-31' }) + const boundaries = [startOfDay('2020-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + + const result = buildTimeline([undated, dated], null, boundaries) + + // At 2020-01-01: both active, only one has dateStart → Dated wins (rule 3) + // At 2021-01-01: only Undated active (Dated ended) → switch to Undated + expect(result).toEqual([ + { + organization: 'Dated', + startDate: startOfDay('2020-01-01').toISOString(), + endDate: startOfDay('2020-12-31').toISOString(), + }, + { + organization: 'Undated', + startDate: startOfDay('2021-01-01').toISOString(), + endDate: null, + }, + ]) + }) + + it('returns an empty array when no rows are active at any boundary', () => { + const result = buildTimeline([], null, [startOfDay('2020-01-01'), startOfDay('2026-01-01')]) + expect(result).toEqual([]) + }) +}) + +// --------------------------------------------------------------------------- +// resolveAffiliationsByMemberIds +// --------------------------------------------------------------------------- + +describe('resolveAffiliationsByMemberIds', () => { + let mockSelect: ReturnType + let mockQx: QueryExecutor + + beforeEach(() => { + mockSelect = vi.fn() + mockQx = { select: mockSelect } as unknown as QueryExecutor + }) + + it('returns an empty affiliations array for a member with no work experiences', async () => { + mockSelect.mockResolvedValueOnce([]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + + expect(result.get('m1')).toEqual([]) + }) + + it('maps member IDs not present in DB rows to empty arrays', async () => { + mockSelect.mockResolvedValueOnce([]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1', 'm2']) + + expect(result.get('m1')).toEqual([]) + expect(result.get('m2')).toEqual([]) + }) + + it('resolves a single ongoing work experience into one affiliation period', async () => { + const row = makeRow({ + memberId: 'm1', + organizationId: 'org1', + organizationName: 'Acme', + dateStart: '2020-01-01', + dateEnd: null, + }) + mockSelect.mockResolvedValueOnce([row]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + const affiliations = result.get('m1') + + expect(affiliations).toHaveLength(1) + expect(affiliations[0].organization).toBe('Acme') + expect(affiliations[0].startDate).toBe(startOfDay('2020-01-01').toISOString()) + expect(affiliations[0].endDate).toBeNull() + }) + + it('filters out work experiences with blacklisted titles', async () => { + const blacklisted = makeRow({ + memberId: 'm1', + organizationName: 'Angel Fund', + dateStart: '2020-01-01', + title: 'Lead Investor', + }) + mockSelect.mockResolvedValueOnce([blacklisted]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + + expect(result.get('m1')).toEqual([]) + }) + + it('returns results sorted newest-first', async () => { + const older = makeRow({ + id: 'we1', + memberId: 'm1', + organizationId: 'org1', + organizationName: 'OldOrg', + dateStart: '2018-01-01', + dateEnd: '2019-12-31', + }) + const newer = makeRow({ + id: 'we2', + memberId: 'm1', + organizationId: 'org2', + organizationName: 'NewOrg', + dateStart: '2021-01-01', + dateEnd: '2022-12-31', + }) + mockSelect.mockResolvedValueOnce([older, newer]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + const affiliations = result.get('m1') + + expect(affiliations[0].organization).toBe('NewOrg') + expect(affiliations[1].organization).toBe('OldOrg') + }) + + it('processes multiple members independently', async () => { + const m1row = makeRow({ id: 'r1', memberId: 'm1', organizationId: 'o1', organizationName: 'Acme', dateStart: '2020-01-01', dateEnd: null }) + const m2row = makeRow({ id: 'r2', memberId: 'm2', organizationId: 'o2', organizationName: 'CERN', dateStart: '2019-01-01', dateEnd: null }) + mockSelect.mockResolvedValueOnce([m1row, m2row]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1', 'm2']) + + expect(result.get('m1')[0].organization).toBe('Acme') + expect(result.get('m2')[0].organization).toBe('CERN') + }) + + it('manual affiliation (segmentId != null) takes priority over a work experience in the overlapping period', async () => { + // Work experience: 2018-01-01 → 2019-12-31 (ended) + // Manual affiliation: 2020-01-01 → 2021-12-31 (sequential, no overlap) + // Both past-dated so the result is date-independent. + const workExp = makeRow({ + id: 'we1', + memberId: 'm1', + organizationId: 'org-work', + organizationName: 'WorkOrg', + dateStart: '2018-01-01', + dateEnd: '2019-12-31', + segmentId: null, + }) + const manual = makeRow({ + id: 'ma1', + memberId: 'm1', + organizationId: 'org-manual', + organizationName: 'ManualOrg', + dateStart: '2020-01-01', + dateEnd: '2021-12-31', + segmentId: 'seg-1', + isPrimaryWorkExperience: false, + }) + mockSelect.mockResolvedValueOnce([workExp]).mockResolvedValueOnce([manual]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + const affiliations = result.get('m1') + + // Newest first: ManualOrg (2020), then WorkOrg (2018) + expect(affiliations).toHaveLength(2) + expect(affiliations[0].organization).toBe('ManualOrg') + expect(affiliations[1].organization).toBe('WorkOrg') + }) + + it('member with only an undated work experience gets a null-dated affiliation', async () => { + const undated = makeRow({ + memberId: 'm1', + organizationId: 'org1', + organizationName: 'Undated Org', + dateStart: null, + dateEnd: null, + }) + mockSelect.mockResolvedValueOnce([undated]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + const affiliations = result.get('m1') + + expect(affiliations).toHaveLength(1) + expect(affiliations[0]).toEqual({ organization: 'Undated Org', startDate: null, endDate: null }) + }) + + // primaryUndated filtering: when one undated work-experience row is marked + // isPrimaryWorkExperience=true, all other undated work-experience rows are + // dropped to avoid infinite conflicts. Manual affiliations (segmentId != null) + // are never dropped regardless. + it('drops other undated work-experience orgs when one undated primary exists', async () => { + const primary = makeRow({ + id: 'primary', + memberId: 'm1', + organizationId: 'org-primary', + organizationName: 'PrimaryUndated', + isPrimaryWorkExperience: true, + dateStart: null, + dateEnd: null, + segmentId: null, + }) + const extra = makeRow({ + id: 'extra', + memberId: 'm1', + organizationId: 'org-extra', + organizationName: 'ExtraUndated', + isPrimaryWorkExperience: false, + dateStart: null, + dateEnd: null, + segmentId: null, + }) + mockSelect.mockResolvedValueOnce([primary, extra]).mockResolvedValueOnce([]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + const affiliations = result.get('m1') + + expect(affiliations).toHaveLength(1) + expect(affiliations[0].organization).toBe('PrimaryUndated') + }) + + it('keeps manual affiliations even when an undated primary work experience is present', async () => { + // The primaryUndated filter drops extra undated *work-experience* rows but must + // never drop manual affiliations (segmentId != null), even if they are also undated. + // Give the manual a dateStart so it participates in the timeline and appears in the result. + const primary = makeRow({ + id: 'primary', + memberId: 'm1', + organizationId: 'org-primary', + organizationName: 'PrimaryUndated', + isPrimaryWorkExperience: true, + dateStart: null, + dateEnd: null, + segmentId: null, + }) + const extra = makeRow({ + id: 'extra', + memberId: 'm1', + organizationId: 'org-extra', + organizationName: 'ExtraUndated', + isPrimaryWorkExperience: false, + dateStart: null, + dateEnd: null, + segmentId: null, + }) + const manual = makeRow({ + id: 'manual', + memberId: 'm1', + organizationId: 'org-manual', + organizationName: 'ManualOrg', + isPrimaryWorkExperience: false, + dateStart: '2020-01-01', + dateEnd: '2021-12-31', + segmentId: 'seg-1', + }) + // primary + extra come from the work-experiences query; manual from the manual affiliations query + mockSelect.mockResolvedValueOnce([primary, extra]).mockResolvedValueOnce([manual]) + + const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) + const orgs = result.get('m1').map((a) => a.organization) + + expect(orgs).toContain('ManualOrg') // manual survived the filter + expect(orgs).not.toContain('ExtraUndated') // extra undated work-exp was dropped + }) + + // Error cases + it('propagates errors thrown by qx.select', async () => { + mockSelect.mockRejectedValueOnce(new Error('DB connection lost')) + + await expect(resolveAffiliationsByMemberIds(mockQx, ['m1'])).rejects.toThrow('DB connection lost') + }) + + it('propagates errors thrown by the second qx.select (manual affiliations query)', async () => { + mockSelect + .mockResolvedValueOnce([]) + .mockRejectedValueOnce(new Error('query timeout')) + + await expect(resolveAffiliationsByMemberIds(mockQx, ['m1'])).rejects.toThrow('query timeout') + }) +}) diff --git a/services/libs/data-access-layer/src/affiliations/index.ts b/services/libs/data-access-layer/src/affiliations/index.ts index a19ff7be12..7f669885c8 100644 --- a/services/libs/data-access-layer/src/affiliations/index.ts +++ b/services/libs/data-access-layer/src/affiliations/index.ts @@ -10,7 +10,7 @@ export interface IAffiliationPeriod { endDate: string | null } -interface IWorkExperienceResolution { +export interface IWorkExperienceResolution { id: string memberId: string organizationId: string @@ -101,7 +101,7 @@ export async function findManualAffiliationsBulk( ) } -function selectPrimaryWorkExperience(orgs: IWorkExperienceResolution[]) { +export function selectPrimaryWorkExperience(orgs: IWorkExperienceResolution[]) { if (orgs.length === 1) return orgs[0] // 1. Manual affiliations (segmentId non-null) always win @@ -199,7 +199,7 @@ function dayBefore(date: Date): Date { } /** Iterates boundary intervals and builds non-overlapping affiliation windows. */ -function buildTimeline( +export function buildTimeline( allRows: IWorkExperienceResolution[], fallbackOrg: IWorkExperienceResolution | null, boundaries: Date[], diff --git a/services/libs/data-access-layer/vitest.config.ts b/services/libs/data-access-layer/vitest.config.ts new file mode 100644 index 0000000000..530cc66032 --- /dev/null +++ b/services/libs/data-access-layer/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.test.ts'], + server: { + deps: { + inline: [/@crowd\//], + }, + }, + }, +}) From b09ecf0ff081668cca2dbf2c14c601ab35340541 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Mon, 30 Mar 2026 14:16:27 +0200 Subject: [PATCH 2/3] fix: lint Signed-off-by: Umberto Sgueglia --- .../__tests__/affiliations.test.ts | 176 ++++++++++++++---- 1 file changed, 141 insertions(+), 35 deletions(-) diff --git a/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts b/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts index 08c7861585..9138b2181f 100644 --- a/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts +++ b/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts @@ -1,5 +1,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { QueryExecutor } from '../../queryExecutor' +import { + buildTimeline, + resolveAffiliationsByMemberIds, + selectPrimaryWorkExperience, +} from '../index' +import type { IWorkExperienceResolution } from '../index' + // Mocks are hoisted before imports — intercept transitive dependencies that // require a live database or external services. vi.mock('@crowd/database', () => ({})) @@ -21,14 +29,6 @@ vi.mock('../../members/base', () => ({ BLACKLISTED_MEMBER_TITLES: ['investor', 'mentor', 'board member'], })) -import type { QueryExecutor } from '../../queryExecutor' -import { - buildTimeline, - resolveAffiliationsByMemberIds, - selectPrimaryWorkExperience, -} from '../index' -import type { IWorkExperienceResolution } from '../index' - // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -70,8 +70,16 @@ describe('selectPrimaryWorkExperience', () => { }) it('picks the manual affiliation (segmentId != null) over work experiences', () => { - const workExp = makeRow({ organizationId: 'work', organizationName: 'Work Inc', segmentId: null }) - const manual = makeRow({ organizationId: 'manual', organizationName: 'Manual Org', segmentId: 'seg-1' }) + const workExp = makeRow({ + organizationId: 'work', + organizationName: 'Work Inc', + segmentId: null, + }) + const manual = makeRow({ + organizationId: 'manual', + organizationName: 'Manual Org', + segmentId: 'seg-1', + }) const result = selectPrimaryWorkExperience([workExp, manual]) expect(result.organizationName).toBe('Manual Org') }) @@ -96,7 +104,11 @@ describe('selectPrimaryWorkExperience', () => { }) it('picks the isPrimaryWorkExperience=true row when no manual affiliations exist', () => { - const plain = makeRow({ organizationId: 'plain', organizationName: 'Plain', dateStart: '2018-01-01' }) + const plain = makeRow({ + organizationId: 'plain', + organizationName: 'Plain', + dateStart: '2018-01-01', + }) const primary = makeRow({ organizationId: 'primary', organizationName: 'Primary', @@ -126,7 +138,11 @@ describe('selectPrimaryWorkExperience', () => { it('picks the single dated org when no primary and only one has a dateStart', () => { const undated = makeRow({ organizationId: 'u', organizationName: 'Undated', dateStart: null }) - const dated = makeRow({ organizationId: 'd', organizationName: 'Dated', dateStart: '2020-01-01' }) + const dated = makeRow({ + organizationId: 'd', + organizationName: 'Dated', + dateStart: '2020-01-01', + }) const result = selectPrimaryWorkExperience([undated, dated]) expect(result.organizationName).toBe('Dated') }) @@ -189,7 +205,11 @@ describe('buildTimeline', () => { }) it('produces a single closed period for an org with both start and end dates', () => { - const org = makeRow({ organizationName: 'Acme', dateStart: '2020-01-01', dateEnd: '2022-12-31' }) + const org = makeRow({ + organizationName: 'Acme', + dateStart: '2020-01-01', + dateEnd: '2022-12-31', + }) // afterEnd boundary: day after dateEnd const boundaries = [startOfDay('2020-01-01'), startOfDay('2023-01-01')] @@ -205,10 +225,24 @@ describe('buildTimeline', () => { }) it('produces two sequential periods when orgs share a boundary with no gap', () => { - const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2020-12-31' }) - const org2 = makeRow({ organizationId: 'o2', organizationName: 'Org2', dateStart: '2021-01-01', dateEnd: null }) + const org1 = makeRow({ + organizationId: 'o1', + organizationName: 'Org1', + dateStart: '2018-01-01', + dateEnd: '2020-12-31', + }) + const org2 = makeRow({ + organizationId: 'o2', + organizationName: 'Org2', + dateStart: '2021-01-01', + dateEnd: null, + }) // 2021-01-01 is both the afterEnd of org1 and the dateStart of org2 - const boundaries = [startOfDay('2018-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + const boundaries = [ + startOfDay('2018-01-01'), + startOfDay('2021-01-01'), + startOfDay('2026-01-01'), + ] const result = buildTimeline([org1, org2], null, boundaries) @@ -227,10 +261,25 @@ describe('buildTimeline', () => { }) it('fills a gap between two orgs with the fallback org', () => { - const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2019-12-31' }) - const org2 = makeRow({ organizationId: 'o2', organizationName: 'Org2', dateStart: '2021-01-01', dateEnd: null }) + const org1 = makeRow({ + organizationId: 'o1', + organizationName: 'Org1', + dateStart: '2018-01-01', + dateEnd: '2019-12-31', + }) + const org2 = makeRow({ + organizationId: 'o2', + organizationName: 'Org2', + dateStart: '2021-01-01', + dateEnd: null, + }) const fallback = makeRow({ organizationId: 'fb', organizationName: 'Fallback' }) - const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + const boundaries = [ + startOfDay('2018-01-01'), + startOfDay('2020-01-01'), + startOfDay('2021-01-01'), + startOfDay('2026-01-01'), + ] const result = buildTimeline([org1, org2], fallback, boundaries) @@ -254,9 +303,24 @@ describe('buildTimeline', () => { }) it('leaves a gap unfilled when there is no fallback org', () => { - const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2019-12-31' }) - const org2 = makeRow({ organizationId: 'o2', organizationName: 'Org2', dateStart: '2021-01-01', dateEnd: null }) - const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + const org1 = makeRow({ + organizationId: 'o1', + organizationName: 'Org1', + dateStart: '2018-01-01', + dateEnd: '2019-12-31', + }) + const org2 = makeRow({ + organizationId: 'o2', + organizationName: 'Org2', + dateStart: '2021-01-01', + dateEnd: null, + }) + const boundaries = [ + startOfDay('2018-01-01'), + startOfDay('2020-01-01'), + startOfDay('2021-01-01'), + startOfDay('2026-01-01'), + ] const result = buildTimeline([org1, org2], null, boundaries) @@ -275,9 +339,18 @@ describe('buildTimeline', () => { }) it('extends the fallback org to cover a trailing gap after the last dated org ends', () => { - const org1 = makeRow({ organizationId: 'o1', organizationName: 'Org1', dateStart: '2018-01-01', dateEnd: '2019-12-31' }) + const org1 = makeRow({ + organizationId: 'o1', + organizationName: 'Org1', + dateStart: '2018-01-01', + dateEnd: '2019-12-31', + }) const fallback = makeRow({ organizationId: 'fb', organizationName: 'Fallback' }) - const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2026-01-01')] + const boundaries = [ + startOfDay('2018-01-01'), + startOfDay('2020-01-01'), + startOfDay('2026-01-01'), + ] const result = buildTimeline([org1], fallback, boundaries) @@ -310,7 +383,12 @@ describe('buildTimeline', () => { dateEnd: null, memberCount: 100, }) - const boundaries = [startOfDay('2018-01-01'), startOfDay('2020-01-01'), startOfDay('2022-01-01'), startOfDay('2026-01-01')] + const boundaries = [ + startOfDay('2018-01-01'), + startOfDay('2020-01-01'), + startOfDay('2022-01-01'), + startOfDay('2026-01-01'), + ] const result = buildTimeline([low, high], null, boundaries) @@ -329,9 +407,23 @@ describe('buildTimeline', () => { }) it('undated org in allRows competes at every boundary and takes over after the dated org ends', () => { - const undated = makeRow({ organizationId: 'undated', organizationName: 'Undated', dateStart: null, dateEnd: null }) - const dated = makeRow({ organizationId: 'dated', organizationName: 'Dated', dateStart: '2020-01-01', dateEnd: '2020-12-31' }) - const boundaries = [startOfDay('2020-01-01'), startOfDay('2021-01-01'), startOfDay('2026-01-01')] + const undated = makeRow({ + organizationId: 'undated', + organizationName: 'Undated', + dateStart: null, + dateEnd: null, + }) + const dated = makeRow({ + organizationId: 'dated', + organizationName: 'Dated', + dateStart: '2020-01-01', + dateEnd: '2020-12-31', + }) + const boundaries = [ + startOfDay('2020-01-01'), + startOfDay('2021-01-01'), + startOfDay('2026-01-01'), + ] const result = buildTimeline([undated, dated], null, boundaries) @@ -447,8 +539,22 @@ describe('resolveAffiliationsByMemberIds', () => { }) it('processes multiple members independently', async () => { - const m1row = makeRow({ id: 'r1', memberId: 'm1', organizationId: 'o1', organizationName: 'Acme', dateStart: '2020-01-01', dateEnd: null }) - const m2row = makeRow({ id: 'r2', memberId: 'm2', organizationId: 'o2', organizationName: 'CERN', dateStart: '2019-01-01', dateEnd: null }) + const m1row = makeRow({ + id: 'r1', + memberId: 'm1', + organizationId: 'o1', + organizationName: 'Acme', + dateStart: '2020-01-01', + dateEnd: null, + }) + const m2row = makeRow({ + id: 'r2', + memberId: 'm2', + organizationId: 'o2', + organizationName: 'CERN', + dateStart: '2019-01-01', + dateEnd: null, + }) mockSelect.mockResolvedValueOnce([m1row, m2row]).mockResolvedValueOnce([]) const result = await resolveAffiliationsByMemberIds(mockQx, ['m1', 'm2']) @@ -582,7 +688,7 @@ describe('resolveAffiliationsByMemberIds', () => { const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) const orgs = result.get('m1').map((a) => a.organization) - expect(orgs).toContain('ManualOrg') // manual survived the filter + expect(orgs).toContain('ManualOrg') // manual survived the filter expect(orgs).not.toContain('ExtraUndated') // extra undated work-exp was dropped }) @@ -590,13 +696,13 @@ describe('resolveAffiliationsByMemberIds', () => { it('propagates errors thrown by qx.select', async () => { mockSelect.mockRejectedValueOnce(new Error('DB connection lost')) - await expect(resolveAffiliationsByMemberIds(mockQx, ['m1'])).rejects.toThrow('DB connection lost') + await expect(resolveAffiliationsByMemberIds(mockQx, ['m1'])).rejects.toThrow( + 'DB connection lost', + ) }) it('propagates errors thrown by the second qx.select (manual affiliations query)', async () => { - mockSelect - .mockResolvedValueOnce([]) - .mockRejectedValueOnce(new Error('query timeout')) + mockSelect.mockResolvedValueOnce([]).mockRejectedValueOnce(new Error('query timeout')) await expect(resolveAffiliationsByMemberIds(mockQx, ['m1'])).rejects.toThrow('query timeout') }) From 4441da030d4f50bf5f1e98e19f688cfe9d6a5e82 Mon Sep 17 00:00:00 2001 From: Umberto Sgueglia Date: Mon, 30 Mar 2026 15:03:14 +0200 Subject: [PATCH 3/3] fix: comments after review Signed-off-by: Umberto Sgueglia --- pnpm-lock.yaml | 213 ++++++++++++------ services/libs/data-access-layer/package.json | 2 +- .../__tests__/affiliations.test.ts | 16 +- 3 files changed, 155 insertions(+), 76 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ad2066130..e19ace1b64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2104,8 +2104,8 @@ importers: specifier: ^5.6.3 version: 5.6.3 vitest: - specifier: ^2.1.9 - version: 2.1.9(@types/node@18.19.31)(terser@5.43.1) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@18.19.31)(terser@5.43.1) services/libs/database: dependencies: @@ -5177,6 +5177,9 @@ packages: '@types/caseless@0.12.5': resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/config@3.3.4': resolution: {integrity: sha512-qFiTLnWy+TdPSMIXFHP+87lFXFRM4SXjRS+CSB66+56TrpLNw003y1sh7DGaaC1NGesxgKoT5FDy6dyA1Xju/g==} @@ -5202,6 +5205,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -5455,34 +5461,34 @@ packages: resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} hasBin: true - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@vladfrangu/async_event_emitter@2.2.4': resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==} @@ -6475,6 +6481,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -7120,6 +7135,15 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -7995,6 +8019,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.2: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true @@ -9071,8 +9098,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} @@ -9153,6 +9180,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -9960,6 +9991,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -10104,16 +10138,20 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} tldts-core@6.1.18: @@ -10445,9 +10483,9 @@ packages: verify-github-webhook@1.0.1: resolution: {integrity: sha512-c5Kv/wzbPBii5s2FyZssgarhMCaHCQAXexqKl2JX/BbCCbZ8oRICS+x9E82pdCNnV9az2sd9SOb+MUtdPNHyvA==} - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite@5.4.21: @@ -10481,20 +10519,23 @@ packages: terser: optional: true - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/debug': + optional: true '@types/node': optional: true '@vitest/browser': @@ -15031,6 +15072,11 @@ snapshots: '@types/caseless@0.12.5': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/config@3.3.4': {} '@types/connect@3.4.38': @@ -15057,6 +15103,8 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/deep-eql@4.0.2': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -15389,45 +15437,47 @@ snapshots: '@vercel/ncc@0.38.1': {} - '@vitest/expect@2.1.9': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.3.3 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1))': + '@vitest/mocker@3.2.4(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1))': dependencies: - '@vitest/spy': 2.1.9 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) - '@vitest/pretty-format@2.1.9': + '@vitest/pretty-format@3.2.4': dependencies: - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/runner@2.1.9': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 - '@vitest/snapshot@2.1.9': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 2.1.9 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.21 - pathe: 1.1.2 + pathe: 2.0.3 - '@vitest/spy@2.1.9': + '@vitest/spy@3.2.4': dependencies: - tinyspy: 3.0.2 + tinyspy: 4.0.4 - '@vitest/utils@2.1.9': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 2.1.9 + '@vitest/pretty-format': 3.2.4 loupe: 3.2.1 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 '@vladfrangu/async_event_emitter@2.2.4': {} @@ -16597,6 +16647,10 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decompress-response@3.3.0: @@ -17468,6 +17522,10 @@ snapshots: dependencies: reusify: 1.0.4 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + fecha@4.2.3: {} fetch-blob@3.2.0: @@ -18414,6 +18472,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.2: dependencies: argparse: 1.0.10 @@ -19514,7 +19574,7 @@ snapshots: path-type@4.0.0: {} - pathe@1.1.2: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -19592,6 +19652,8 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.4: {} + pidtree@0.6.0: {} pidusage@3.0.2: @@ -20575,6 +20637,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + strnum@1.0.5: {} strnum@2.1.2: {} @@ -20759,11 +20825,16 @@ snapshots: tinyexec@0.3.2: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.1.1: {} - tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} - tinyspy@3.0.2: {} + tinyspy@4.0.4: {} tldts-core@6.1.18: {} @@ -21085,12 +21156,12 @@ snapshots: verify-github-webhook@1.0.1: {} - vite-node@2.1.9(@types/node@18.19.31)(terser@5.43.1): + vite-node@3.2.4(@types/node@18.19.31)(terser@5.43.1): dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3 es-module-lexer: 1.7.0 - pathe: 1.1.2 + pathe: 2.0.3 vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) transitivePeerDependencies: - '@types/node' @@ -21113,29 +21184,33 @@ snapshots: fsevents: 2.3.3 terser: 5.43.1 - vitest@2.1.9(@types/node@18.19.31)(terser@5.43.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@18.19.31)(terser@5.43.1): dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@18.19.31)(terser@5.43.1)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.3.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.3 expect-type: 1.3.0 magic-string: 0.30.21 - pathe: 1.1.2 + pathe: 2.0.3 + picomatch: 4.0.4 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 + tinyglobby: 0.2.15 tinypool: 1.1.1 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 vite: 5.4.21(@types/node@18.19.31)(terser@5.43.1) - vite-node: 2.1.9(@types/node@18.19.31)(terser@5.43.1) + vite-node: 3.2.4(@types/node@18.19.31)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 '@types/node': 18.19.31 transitivePeerDependencies: - less diff --git a/services/libs/data-access-layer/package.json b/services/libs/data-access-layer/package.json index f62e8d5faa..ab3296583e 100644 --- a/services/libs/data-access-layer/package.json +++ b/services/libs/data-access-layer/package.json @@ -38,6 +38,6 @@ "@types/node": "^18.16.3", "sequelize": "6.37.8", "typescript": "^5.6.3", - "vitest": "^2.1.9" + "vitest": "^3.2.4" } } diff --git a/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts b/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts index 9138b2181f..4edf77b0cb 100644 --- a/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts +++ b/services/libs/data-access-layer/src/affiliations/__tests__/affiliations.test.ts @@ -564,16 +564,17 @@ describe('resolveAffiliationsByMemberIds', () => { }) it('manual affiliation (segmentId != null) takes priority over a work experience in the overlapping period', async () => { - // Work experience: 2018-01-01 → 2019-12-31 (ended) - // Manual affiliation: 2020-01-01 → 2021-12-31 (sequential, no overlap) - // Both past-dated so the result is date-independent. + // Work experience: 2018-01-01 → 2021-12-31 + // Manual affiliation: 2020-01-01 → 2021-12-31 + // They overlap from 2020-01-01; in that window the manual must win. + // Both end in the past so the result is date-independent. const workExp = makeRow({ id: 'we1', memberId: 'm1', organizationId: 'org-work', organizationName: 'WorkOrg', dateStart: '2018-01-01', - dateEnd: '2019-12-31', + dateEnd: '2021-12-31', segmentId: null, }) const manual = makeRow({ @@ -584,17 +585,20 @@ describe('resolveAffiliationsByMemberIds', () => { dateStart: '2020-01-01', dateEnd: '2021-12-31', segmentId: 'seg-1', - isPrimaryWorkExperience: false, }) mockSelect.mockResolvedValueOnce([workExp]).mockResolvedValueOnce([manual]) const result = await resolveAffiliationsByMemberIds(mockQx, ['m1']) const affiliations = result.get('m1') - // Newest first: ManualOrg (2020), then WorkOrg (2018) + // Newest first: + // ManualOrg 2020-01-01 → 2021-12-31 (manual won the overlap) + // WorkOrg 2018-01-01 → 2019-12-31 (work exp before manual started) expect(affiliations).toHaveLength(2) expect(affiliations[0].organization).toBe('ManualOrg') + expect(affiliations[0].startDate).toBe(startOfDay('2020-01-01').toISOString()) expect(affiliations[1].organization).toBe('WorkOrg') + expect(affiliations[1].endDate).toBe(startOfDay('2019-12-31').toISOString()) }) it('member with only an undated work experience gets a null-dated affiliation', async () => {