chore: Jest Testing structure (#2707)

* deps: add jest deps

* chore: mock

* feat: use mocinggoose

* feat: jest

* chore: remove babel.config.js
This commit is contained in:
Finley Ge
2024-09-19 13:34:03 +08:00
committed by GitHub
parent 265434799f
commit ca9f36ef9f
18 changed files with 5257 additions and 26 deletions

View File

View File

@@ -7,3 +7,7 @@ export type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>>;
}[Keys];
export type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};

262
pnpm-lock.yaml generated
View File

@@ -22,7 +22,7 @@ importers:
version: 13.3.0
next-i18next:
specifier: 15.3.0
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
prettier:
specifier: 3.2.4
version: 3.2.4
@@ -61,7 +61,7 @@ importers:
version: 4.0.2
next:
specifier: 14.2.5
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
openai:
specifier: 4.61.0
version: 4.61.0(encoding@0.1.13)
@@ -180,7 +180,7 @@ importers:
version: 1.4.5-lts.1
next:
specifier: 14.2.5
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
nextjs-cors:
specifier: ^2.2.0
version: 2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))
@@ -250,7 +250,7 @@ importers:
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
specifier: 2.1.5
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
'@chakra-ui/react':
specifier: 2.8.1
version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -313,7 +313,7 @@ importers:
version: 4.17.21
next-i18next:
specifier: 15.3.0
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
papaparse:
specifier: ^5.4.1
version: 5.4.1
@@ -374,7 +374,7 @@ importers:
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
specifier: 2.1.5
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
'@chakra-ui/react':
specifier: 2.8.1
version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -411,6 +411,9 @@ importers:
'@tanstack/react-query':
specifier: ^4.24.10
version: 4.36.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/jest':
specifier: ^29.5.2
version: 29.5.12
'@types/nprogress':
specifier: ^0.2.0
version: 0.2.3
@@ -447,6 +450,9 @@ importers:
immer:
specifier: ^9.0.19
version: 9.0.21
jest:
specifier: ^29.5.0
version: 29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3))
js-yaml:
specifier: ^4.1.0
version: 4.1.0
@@ -467,10 +473,10 @@ importers:
version: 4.0.2
next:
specifier: 14.2.5
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
version: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
next-i18next:
specifier: 15.3.0
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
nextjs-node-loader:
specifier: ^1.1.5
version: 1.1.5(webpack@5.92.1)
@@ -522,6 +528,9 @@ importers:
sass:
specifier: ^1.58.3
version: 1.77.8
ts-jest:
specifier: ^29.1.0
version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3)
use-context-selector:
specifier: ^1.4.4
version: 1.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2)
@@ -529,6 +538,9 @@ importers:
specifier: ^4.3.5
version: 4.5.4(@types/react@18.3.1)(immer@9.0.21)(react@18.3.1)
devDependencies:
'@shelf/jest-mongodb':
specifier: ^4.3.2
version: 4.3.2(jest-environment-node@29.7.0)(mongodb@5.9.2)
'@svgr/webpack':
specifier: ^6.5.1
version: 6.5.1
@@ -565,6 +577,9 @@ importers:
eslint-config-next:
specifier: 14.2.3
version: 14.2.3(eslint@8.56.0)(typescript@5.5.3)
mockingoose:
specifier: ^2.16.2
version: 2.16.2(mongoose@7.7.0)
typescript:
specifier: ^5.1.3
version: 5.5.3
@@ -2994,6 +3009,13 @@ packages:
'@rushstack/eslint-patch@1.10.3':
resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==}
'@shelf/jest-mongodb@4.3.2':
resolution: {integrity: sha512-LL7NBaT04sJspoOZXqw3HGLw0+XnZNlIV72x2ymzyuloqIKXwgUl8eL1XKDUh4Ud8dUBRMrOngCQBcHKjWnrHQ==}
engines: {node: '>=16'}
peerDependencies:
jest-environment-node: 28.x || 29.x
mongodb: 3.x.x || 4.x || 5.x || 6.x
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -3829,6 +3851,9 @@ packages:
ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
async-mutex@0.4.1:
resolution: {integrity: sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==}
async@3.2.5:
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
@@ -3856,6 +3881,9 @@ packages:
axobject-query@3.1.1:
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
b4a@1.6.6:
resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==}
babel-jest@29.7.0:
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3909,6 +3937,9 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
bare-events@2.4.2:
resolution: {integrity: sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -4253,6 +4284,9 @@ packages:
resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==}
engines: {node: '>= 6'}
commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
@@ -5086,6 +5120,9 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
@@ -5174,6 +5211,10 @@ packages:
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
engines: {node: '>= 0.8'}
find-cache-dir@3.3.2:
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
engines: {node: '>=8'}
find-my-way@8.2.0:
resolution: {integrity: sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==}
engines: {node: '>=14'}
@@ -6718,12 +6759,26 @@ packages:
mnemonist@0.39.6:
resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
mockingoose@2.16.2:
resolution: {integrity: sha512-6JokLoDoH4yzwnZYLwoynU2lNmbfW7Mcf0d8sF580XI2hqE9Cs5l22iFTjLB51cdxkOvRtjoL84LLVEGmgcwKQ==}
engines: {node: '>=6.4.0'}
peerDependencies:
mongoose: '>=4.9.10'
monaco-editor@0.50.0:
resolution: {integrity: sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA==}
mongodb-connection-string-url@2.6.0:
resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
mongodb-memory-server-core@9.2.0:
resolution: {integrity: sha512-9SWZEy+dGj5Fvm5RY/mtqHZKS64o4heDwReD4SsfR7+uNgtYo+JN41kPCcJeIH3aJf04j25i5Dia2s52KmsMPA==}
engines: {node: '>=14.20.1'}
mongodb-memory-server@9.2.0:
resolution: {integrity: sha512-w/usKdYtby5EALERxmA0+et+D0brP0InH3a26shNDgGefXA61hgl6U0P3IfwqZlEGRZdkbZig3n57AHZgDiwvg==}
engines: {node: '>=14.20.1'}
mongodb@5.9.2:
resolution: {integrity: sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==}
engines: {node: '>=14.20.1'}
@@ -6812,6 +6867,10 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
new-find-package-json@2.0.0:
resolution: {integrity: sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==}
engines: {node: '>=12.22.0'}
next-i18next@15.3.0:
resolution: {integrity: sha512-bq7Cc9XJFcmGOCLnyEtHaeJ3+JJNsI/8Pkj9BaHAnhm4sZ9vNNC4ZsaqYnlRZ7VH5ypSo73fEqLK935jLsmCvQ==}
engines: {node: '>=14'}
@@ -7380,6 +7439,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
@@ -7960,6 +8022,9 @@ packages:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
streamx@2.20.0:
resolution: {integrity: sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==}
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
@@ -8125,6 +8190,9 @@ packages:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
tar-stream@3.1.7:
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
@@ -8154,6 +8222,9 @@ packages:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
text-decoder@1.1.1:
resolution: {integrity: sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==}
text-hex@1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
@@ -8826,6 +8897,10 @@ packages:
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
yauzl@3.1.3:
resolution: {integrity: sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==}
engines: {node: '>=12'}
yjs@13.6.18:
resolution: {integrity: sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg==}
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
@@ -10087,12 +10162,20 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
'@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
'@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
dependencies:
'@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/cache': 11.11.0
'@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1)
next: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1
'@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
dependencies:
'@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/cache': 11.11.0
'@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1)
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1
'@chakra-ui/number-input@2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)':
@@ -11709,6 +11792,20 @@ snapshots:
'@rushstack/eslint-patch@1.10.3': {}
'@shelf/jest-mongodb@4.3.2(jest-environment-node@29.7.0)(mongodb@5.9.2)':
dependencies:
debug: 4.3.4
jest-environment-node: 29.7.0
mongodb: 5.9.2
mongodb-memory-server: 9.2.0
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- kerberos
- mongodb-client-encryption
- snappy
- supports-color
'@sinclair/typebox@0.27.8': {}
'@sinonjs/commons@3.0.1':
@@ -12747,6 +12844,10 @@ snapshots:
ast-types-flow@0.0.8: {}
async-mutex@0.4.1:
dependencies:
tslib: 2.6.3
async@3.2.5: {}
asynckit@0.4.0: {}
@@ -12766,7 +12867,7 @@ snapshots:
axios@1.7.2:
dependencies:
follow-redirects: 1.15.6
follow-redirects: 1.15.6(debug@4.3.5)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -12776,6 +12877,8 @@ snapshots:
dependencies:
deep-equal: 2.2.3
b4a@1.6.6: {}
babel-jest@29.7.0(@babel/core@7.24.9):
dependencies:
'@babel/core': 7.24.9
@@ -12864,6 +12967,9 @@ snapshots:
balanced-match@1.0.2: {}
bare-events@2.4.2:
optional: true
base64-js@1.5.1: {}
big.js@5.2.2: {}
@@ -13231,6 +13337,8 @@ snapshots:
has-own-prop: 2.0.0
repeat-string: 1.6.1
commondir@1.0.1: {}
component-emitter@1.3.1: {}
compute-scroll-into-view@3.0.3: {}
@@ -14343,6 +14451,8 @@ snapshots:
fast-deep-equal@3.1.3: {}
fast-fifo@1.3.2: {}
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -14475,6 +14585,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
find-cache-dir@3.3.2:
dependencies:
commondir: 1.0.1
make-dir: 3.1.0
pkg-dir: 4.2.0
find-my-way@8.2.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -14507,7 +14623,9 @@ snapshots:
dependencies:
tslib: 2.6.3
follow-redirects@1.15.6: {}
follow-redirects@1.15.6(debug@4.3.5):
optionalDependencies:
debug: 4.3.5
for-each@0.3.3:
dependencies:
@@ -15886,7 +16004,6 @@ snapshots:
make-dir@3.1.0:
dependencies:
semver: 6.3.1
optional: true
make-dir@4.0.0:
dependencies:
@@ -16608,6 +16725,10 @@ snapshots:
dependencies:
obliterator: 2.0.4
mockingoose@2.16.2(mongoose@7.7.0):
dependencies:
mongoose: 7.7.0
monaco-editor@0.50.0: {}
mongodb-connection-string-url@2.6.0:
@@ -16615,6 +16736,40 @@ snapshots:
'@types/whatwg-url': 8.2.2
whatwg-url: 11.0.0
mongodb-memory-server-core@9.2.0:
dependencies:
async-mutex: 0.4.1
camelcase: 6.3.0
debug: 4.3.5
find-cache-dir: 3.3.2
follow-redirects: 1.15.6(debug@4.3.5)
https-proxy-agent: 7.0.5
mongodb: 5.9.2
new-find-package-json: 2.0.0
semver: 7.6.3
tar-stream: 3.1.7
tslib: 2.6.3
yauzl: 3.1.3
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- kerberos
- mongodb-client-encryption
- snappy
- supports-color
mongodb-memory-server@9.2.0:
dependencies:
mongodb-memory-server-core: 9.2.0
tslib: 2.6.3
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- kerberos
- mongodb-client-encryption
- snappy
- supports-color
mongodb@5.9.2:
dependencies:
bson: 5.5.1
@@ -16690,7 +16845,7 @@ snapshots:
neo-async@2.6.2: {}
next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.24.8
'@types/hoist-non-react-statics': 3.3.5
@@ -16698,11 +16853,23 @@ snapshots:
hoist-non-react-statics: 3.3.2
i18next: 23.11.5
i18next-fs-backend: 2.3.1
next: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
next: 14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1
react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8):
next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.24.8
'@types/hoist-non-react-statics': 3.3.5
core-js: 3.37.1
hoist-non-react-statics: 3.3.2
i18next: 23.11.5
i18next-fs-backend: 2.3.1
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1
react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next@14.2.5(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8):
dependencies:
'@next/env': 14.2.5
'@swc/helpers': 0.5.5
@@ -16712,7 +16879,33 @@ snapshots:
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.1(@babel/core@7.24.9)(react@18.3.1)
styled-jsx: 5.1.1(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 14.2.5
'@next/swc-darwin-x64': 14.2.5
'@next/swc-linux-arm64-gnu': 14.2.5
'@next/swc-linux-arm64-musl': 14.2.5
'@next/swc-linux-x64-gnu': 14.2.5
'@next/swc-linux-x64-musl': 14.2.5
'@next/swc-win32-arm64-msvc': 14.2.5
'@next/swc-win32-ia32-msvc': 14.2.5
'@next/swc-win32-x64-msvc': 14.2.5
sass: 1.77.8
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8):
dependencies:
'@next/env': 14.2.5
'@swc/helpers': 0.5.5
busboy: 1.6.0
caniuse-lite: 1.0.30001642
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.1(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 14.2.5
'@next/swc-darwin-x64': 14.2.5
@@ -16731,7 +16924,7 @@ snapshots:
nextjs-cors@2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)):
dependencies:
cors: 2.8.5
next: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
nextjs-node-loader@1.1.5(webpack@5.92.1):
dependencies:
@@ -17291,6 +17484,8 @@ snapshots:
queue-microtask@1.2.3: {}
queue-tick@1.0.1: {}
quick-format-unescaped@4.0.4: {}
raf-schd@4.0.3: {}
@@ -17985,6 +18180,14 @@ snapshots:
streamsearch@1.1.0: {}
streamx@2.20.0:
dependencies:
fast-fifo: 1.3.2
queue-tick: 1.0.1
text-decoder: 1.1.1
optionalDependencies:
bare-events: 2.4.2
string-argv@0.3.2: {}
string-length@4.0.2:
@@ -18098,12 +18301,18 @@ snapshots:
dependencies:
inline-style-parser: 0.2.3
styled-jsx@5.1.1(@babel/core@7.24.9)(react@18.3.1):
styled-jsx@5.1.1(@babel/core@7.24.9)(babel-plugin-macros@3.1.0)(react@18.3.1):
dependencies:
client-only: 0.0.1
react: 18.3.1
optionalDependencies:
'@babel/core': 7.24.9
babel-plugin-macros: 3.1.0
styled-jsx@5.1.1(react@18.3.1):
dependencies:
client-only: 0.0.1
react: 18.3.1
stylis@4.2.0: {}
@@ -18188,6 +18397,12 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
tar-stream@3.1.7:
dependencies:
b4a: 1.6.6
fast-fifo: 1.3.2
streamx: 2.20.0
tar@6.2.1:
dependencies:
chownr: 2.0.0
@@ -18219,6 +18434,10 @@ snapshots:
glob: 7.2.3
minimatch: 3.1.2
text-decoder@1.1.1:
dependencies:
b4a: 1.6.6
text-hex@1.0.0: {}
text-table@0.2.0: {}
@@ -18931,6 +19150,11 @@ snapshots:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
yauzl@3.1.3:
dependencies:
buffer-crc32: 0.2.13
pend: 1.2.0
yjs@13.6.18:
dependencies:
lib0: 0.2.94

184
projects/app/jest.config.js Normal file
View File

@@ -0,0 +1,184 @@
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
const esModules = ['nanoid'].join('|');
/** @type {import('jest').Config} */
const config = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/tmp/jest_rs",
// Automatically clear mock calls, instances, contexts and results before every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: './tmp/coverage',
// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: ['/node_modules/', '/__mocks__/', '/src/test/'],
// Indicates which provider should be used to instrument code for coverage
// coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
moduleDirectories: ['node_modules', 'src'],
// An array of file extensions your modules use
// moduleFileExtensions: ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'node'],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'^nanoid(/(.*)|$)': 'nanoid$1'
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'node',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: ['/node_modules/'],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: [`/node_modules/(?!${esModules})`]
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
module.exports = config;

View File

@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "jest"
},
"dependencies": {
"@bany/curl-to-json": "^1.2.8",
@@ -25,6 +26,7 @@
"@fortaine/fetch-event-source": "^3.0.6",
"@node-rs/jieba": "1.10.0",
"@tanstack/react-query": "^4.24.10",
"@types/jest": "^29.5.2",
"@types/nprogress": "^0.2.0",
"ahooks": "^3.7.11",
"axios": "^1.5.1",
@@ -37,6 +39,7 @@
"hyperdown": "^2.4.29",
"i18next": "23.11.5",
"immer": "^9.0.19",
"jest": "^29.5.0",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.2",
@@ -62,10 +65,12 @@
"remark-math": "^6.0.0",
"request-ip": "^3.3.0",
"sass": "^1.58.3",
"ts-jest": "^29.1.0",
"use-context-selector": "^1.4.4",
"zustand": "^4.3.5"
},
"devDependencies": {
"@shelf/jest-mongodb": "^4.3.2",
"@svgr/webpack": "^6.5.1",
"@types/formidable": "^2.0.5",
"@types/js-yaml": "^4.0.9",
@@ -78,6 +83,8 @@
"@types/request-ip": "^0.0.37",
"eslint": "8.56.0",
"eslint-config-next": "14.2.3",
"mockingoose": "^2.16.2",
"mongodb-memory-server": "^10.0.0",
"nextjs-node-loader": "^1.1.5",
"typescript": "^5.1.3"
}

View File

@@ -0,0 +1,76 @@
import { MongoMemoryServer } from 'mongodb-memory-server';
import mongoose from 'mongoose';
import { MockParseHeaderCert } from '@/test/utils';
import { initMockData } from './db/init';
jest.mock('nanoid', () => {
return {
nanoid: () => {}
};
});
jest.mock('@fastgpt/global/common/string/tools', () => {
return {
hashStr(str: string) {
return str;
}
};
});
jest.mock('@fastgpt/service/common/system/log', jest.fn());
jest.mock('@fastgpt/service/support/permission/controller', () => {
return {
parseHeaderCert: MockParseHeaderCert,
getResourcePermission: jest.requireActual('@fastgpt/service/support/permission/controller')
.getResourcePermission,
getResourceAllClbs: jest.requireActual('@fastgpt/service/support/permission/controller')
.getResourceAllClbs
};
});
const parse = jest.createMockFromModule('@fastgpt/service/support/permission/controller') as any;
parse.parseHeaderCert = MockParseHeaderCert;
jest.mock('@/service/middleware/entry', () => {
return {
NextAPI: (...args: any) => {
return async function api(req: any, res: any) {
try {
let response = null;
for (const handler of args) {
response = await handler(req, res);
}
return {
code: 200,
data: response
};
} catch (error) {
return {
code: 500,
error
};
}
};
}
};
});
beforeAll(async () => {
if (!global.mongod || !global.mongodb) {
const mongod = await MongoMemoryServer.create();
global.mongod = mongod;
global.mongodb = mongoose;
await global.mongodb.connect(mongod.getUri());
await initMockData();
}
});
afterAll(async () => {
if (global.mongodb) {
await global.mongodb.disconnect();
}
if (global.mongod) {
await global.mongod.stop();
}
});

View File

@@ -0,0 +1,48 @@
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
export const root = {
uid: '',
tmbId: '',
teamId: '',
isRoot: true,
appId: ''
};
export const initMockData = async () => {
// init root user
const rootUser = await MongoUser.create({
username: 'root',
password: '123456'
});
const rootTeam = await MongoTeam.create({
name: 'root-default-team',
ownerId: rootUser._id
});
const rootTeamMember = await MongoTeamMember.create({
teamId: rootTeam._id,
userId: rootUser._id,
name: 'root-default-team-member',
status: 'active',
role: TeamMemberRoleEnum.owner
});
const rootApp = await MongoApp.create({
name: 'root-default-app',
teamId: rootTeam._id,
tmbId: rootTeam._id,
type: 'advanced'
});
root.uid = rootUser._id;
root.tmbId = rootTeamMember._id;
root.teamId = rootTeam._id;
root.appId = rootApp._id;
await Promise.all([rootUser.save(), rootTeam.save(), rootTeamMember.save(), rootApp.save()]);
};

View File

@@ -0,0 +1,4 @@
import { MongoMemoryServer } from 'mongodb-memory-server';
declare global {
var mongod: MongoMemoryServer | undefined;
}

View File

@@ -0,0 +1,67 @@
import '../../__mocks__/base';
import { root } from '../../__mocks__/db/init';
import { getTestRequest } from '@/test/utils';
import type { OutLinkListQuery } from './list';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import handler from './list';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
beforeAll(async () => {
await MongoOutLink.create({
shareId: 'aaa',
appId: root.appId,
tmbId: root.tmbId,
teamId: root.teamId,
type: 'share',
name: 'aaa'
});
await MongoOutLink.create({
shareId: 'bbb',
appId: root.appId,
tmbId: root.tmbId,
teamId: root.teamId,
type: 'share',
name: 'bbb'
});
});
test('Should return a list of outLink', async () => {
const res = (await handler(
...getTestRequest<OutLinkListQuery>({
query: {
appId: root.appId,
type: 'share'
},
user: root
})
)) as any;
expect(res.code).toBe(200);
expect(res.data.length).toBe(2);
});
test('appId is required', async () => {
const res = (await handler(
...getTestRequest<OutLinkListQuery>({
query: {
type: 'share'
},
user: root
})
)) as any;
expect(res.code).toBe(500);
expect(res.error).toBe(AppErrEnum.unExist);
});
test('if type is not provided, return nothing', async () => {
const res = (await handler(
...getTestRequest<OutLinkListQuery>({
query: {
appId: root.appId
},
user: root
})
)) as any;
expect(res.code).toBe(200);
expect(res.data.length).toBe(0);
});

View File

@@ -4,6 +4,7 @@ import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
export const ApiMetadata = {
name: '获取应用内所有 Outlink',
@@ -11,19 +12,18 @@ export const ApiMetadata = {
version: '0.1.0'
};
// Outlink
export type OutLinkListQuery = {
appId: string; // 应用 ID
type: string; // 类型
type: `${PublishChannelEnum}`;
};
export type OutLinkListBody = {};
// 响应: 应用内全部 Outlink
// 应用内全部 Outlink 列表
export type OutLinkListResponse = OutLinkSchema[];
// 查询应用内全部 Outlink
async function handler(
// 查询应用的所有 OutLink
export async function handler(
req: ApiRequestProps<OutLinkListBody, OutLinkListQuery>
): Promise<OutLinkListResponse> {
const { appId, type } = req.query;
@@ -43,4 +43,5 @@ async function handler(
return data;
}
export default NextAPI(handler);

View File

@@ -0,0 +1,48 @@
import { getTestRequest } from '@/test/utils';
import '../../__mocks__/base';
import handler, { OutLinkUpdateBody, OutLinkUpdateQuery } from './update';
import { root } from '../../__mocks__/db/init';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
test('Update Outlink', async () => {
const outlink = await MongoOutLink.create({
shareId: 'aaa',
appId: root.appId,
tmbId: root.tmbId,
teamId: root.teamId,
type: 'share',
name: 'aaa'
});
await outlink.save();
const res = (await handler(
...getTestRequest<OutLinkUpdateQuery, OutLinkUpdateBody>({
body: {
_id: outlink._id,
name: 'changed'
},
user: root
})
)) as any;
expect(res.code).toBe(200);
const link = await MongoOutLink.findById(outlink._id).lean();
expect(link?.name).toBe('changed');
});
test('Did not post _id', async () => {
const res = (await handler(
...getTestRequest<OutLinkUpdateQuery, OutLinkUpdateBody>({
body: {
name: 'changed'
},
user: root
})
)) as any;
expect(res.code).toBe(500);
expect(res.error).toBe(CommonErrEnum.missingParams);
});

View File

@@ -7,7 +7,18 @@ import { NextAPI } from '@/service/middleware/entry';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
export type OutLinkUpdateQuery = {};
export type OutLinkUpdateBody = OutLinkEditType & {};
// {
// _id?: string; // Outlink 的 ID
// name: string; // Outlink 的名称
// responseDetail?: boolean; // 是否开启详细回复
// immediateResponse?: string; // 立即回复的内容
// defaultResponse?: string; // 默认回复的内容
// limit?: OutLinkSchema<T>['limit']; // 限制
// app?: T; // 平台的配置
// }
export type OutLinkUpdateBody = OutLinkEditType;
export type OutLinkUpdateResponse = {};
async function handler(

View File

@@ -0,0 +1,94 @@
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
export type TestTokenType = {
userId: string;
teamId: string;
tmbId: string;
isRoot: boolean;
};
export type TestRequest = {
headers: {
cookie?: {
token?: TestTokenType;
};
authorization?: string; // testkey
rootkey?: string; // rootkey
};
query: {
[key: string]: string;
};
body: {
[key: string]: string;
};
};
export function getTestRequest<Q = any, B = any>({
query = {},
body = {},
authToken = true,
// authRoot = false,
// authApiKey = false,
user
}: {
body?: Partial<B>;
query?: Partial<Q>;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
user?: {
uid: string;
tmbId: string;
teamId: string;
isRoot: boolean;
};
}): [any, any] {
const headers: TestRequest['headers'] = {};
if (authToken) {
headers.cookie = {
token: {
userId: String(user?.uid || ''),
teamId: String(user?.teamId || ''),
tmbId: String(user?.tmbId || ''),
isRoot: user?.isRoot || false
}
};
}
return [
{
headers,
query,
body
},
{}
];
}
export const MockParseHeaderCert = async ({
req,
authToken = true,
authRoot = false,
authApiKey = false
}: {
req: TestRequest;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
}): Promise<TestTokenType> => {
if (authToken) {
const token = req.headers?.cookie?.token;
if (!token) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return token;
}
// if (authRoot) {
// // TODO: unfinished
// return req.headers.rootkey;
// }
// if (authApiKey) {
// // TODO: unfinished
// return req.headers.authorization;
// }
return {} as any;
};

49
scripts/openapi/index.js Normal file
View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var utils_1 = require("./utils");
var fs = require("fs");
var path = require("path");
var openapi_1 = require("./openapi");
var rootPath = 'projects/app/src/pages/api';
var exclude = ['/admin', '/proApi'];
function getAllFiles(dir) {
var files = [];
var stat = fs.statSync(dir);
if (stat.isDirectory()) {
var list = fs.readdirSync(dir);
list.forEach(function (item) {
var fullPath = path.join(dir, item);
if (!exclude.some(function (excluded) { return fullPath.includes(excluded); })) {
files = files.concat(getAllFiles(fullPath));
}
});
}
else {
files.push(dir);
}
return files;
}
var searchPath = process.env.SEARCH_PATH || '';
var files = getAllFiles(path.join(rootPath, searchPath));
// console.log(files)
var apis = files.map(function (file) {
return (0, utils_1.parseAPI)({ path: file, rootPath: rootPath });
});
var openapi = (0, openapi_1.convertOpenApi)({
apis: apis,
openapi: '3.0.0',
info: {
title: 'FastGPT OpenAPI',
version: '1.0.0',
author: 'FastGPT'
},
servers: [
{
url: 'http://localhost:4000'
}
]
});
var json = JSON.stringify(openapi, null, 2);
fs.writeFileSync('./scripts/openapi/openapi.json', json);
fs.writeFileSync('./scripts/openapi/openapi.out', JSON.stringify(apis, null, 2));
console.log('Total APIs:', files.length);

145
scripts/openapi/openapi.js Normal file
View File

@@ -0,0 +1,145 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertPath = convertPath;
exports.convertOpenApi = convertOpenApi;
function convertPath(api) {
var _a;
var _b;
var method = api.method.toLowerCase();
var parameters = [];
if (api.query) {
if (Array.isArray(api.query)) {
api.query.forEach(function (item) {
parameters.push({
name: item.key,
description: item.comment,
in: 'query',
required: item.required,
schema: {
type: item.type
}
});
});
}
else {
parameters.push({
description: api.query.comment,
name: api.query.key,
in: 'query',
required: api.query.required,
schema: {
type: api.query.type
}
});
}
}
else if (api.body) {
if (Array.isArray(api.body)) {
api.body.forEach(function (item) {
parameters.push({
description: item.comment,
name: item.key,
in: 'body',
required: item.required,
schema: {
type: item.type
}
});
});
}
}
var responses = (function () {
var _a, _b, _c;
if (api.response) {
if (Array.isArray(api.response)) {
var properties_1 = {};
api.response.forEach(function (item) {
var _a;
properties_1[item.type] = {
type: (_a = item.key) !== null && _a !== void 0 ? _a : item.type,
description: item.comment
};
});
var res = {
'200': {
description: (_a = api.description) !== null && _a !== void 0 ? _a : '',
content: {
'application/json': {
schema: {
type: 'object',
properties: properties_1
}
}
}
}
};
return res;
}
else {
return {
'200': {
description: (_b = api.response.comment) !== null && _b !== void 0 ? _b : '',
content: {
'application/json': {
schema: {
type: api.response.type
}
}
}
}
};
}
}
else {
return {
'200': {
description: (_c = api.description) !== null && _c !== void 0 ? _c : '',
content: {
'application/json': {
schema: {
type: 'object'
}
}
}
}
};
}
})();
return _a = {},
_a[method] = {
description: (_b = api.description) !== null && _b !== void 0 ? _b : '',
parameters: parameters,
responses: responses
},
_a;
}
function convertOpenApi(_a) {
var apis = _a.apis, rest = __rest(_a, ["apis"]);
var paths = {};
apis.forEach(function (api) {
paths[api.url] = convertPath(api);
});
return __assign({ paths: paths }, rest);
}

2756
scripts/openapi/openapi.json Normal file

File diff suppressed because it is too large Load Diff

1284
scripts/openapi/openapi.out Normal file

File diff suppressed because it is too large Load Diff

229
scripts/openapi/utils.js Normal file
View File

@@ -0,0 +1,229 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseAPI = parseAPI;
var parser_1 = require("@babel/parser");
var traverse_1 = require("@babel/traverse");
var fs = require("fs");
function getMetadata(path) {
var _a, _b, _c;
var metadata = {
name: '',
author: '',
version: '',
method: ''
};
if (path.isExportNamedDeclaration() && // get metadata
((_a = path.node.declaration) === null || _a === void 0 ? void 0 : _a.type) === 'VariableDeclaration' &&
((_b = path.node.declaration.declarations[0]) === null || _b === void 0 ? void 0 : _b.id.type) === 'Identifier' &&
path.node.declaration.declarations[0].id.name === 'ApiMetadata' &&
((_c = path.node.declaration.declarations[0].init) === null || _c === void 0 ? void 0 : _c.type) === 'ObjectExpression') {
path.node.declaration.declarations[0].init.properties.forEach(function (item) {
if (item.type === 'ObjectProperty') {
var key = item.key.type === 'Identifier' ? item.key.name : item.key.type;
if (key === 'name') {
metadata.name = item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
}
if (key === 'author') {
metadata.author =
item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
}
if (key === 'version') {
metadata.version =
item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
}
else if (key === 'method') {
metadata.method =
item.value.type === 'StringLiteral' ? item.value.value : item.value.type;
metadata.method = metadata.method.toUpperCase();
}
}
});
if (metadata.name && metadata.author && metadata.version) {
return metadata;
}
}
}
function getDescription(path) {
var _a, _b;
if (path.isFunctionDeclaration() && ((_a = path.node.id) === null || _a === void 0 ? void 0 : _a.name) === 'handler') {
var comments = (_b = path.node.leadingComments) === null || _b === void 0 ? void 0 : _b.map(function (item) { return item.value.trim(); }).join('\n');
return comments;
}
}
function parseType(type) {
if (!type) {
return '';
}
if (type.type === 'TSTypeReference') {
return type.typeName.type === 'Identifier' ? type.typeName.name : type.typeName.type;
}
else if (type.type === 'TSArrayType') {
return "".concat(parseType(type.elementType), "[]");
}
else if (type.type === 'TSUnionType') {
return type.types.map(function (item) { return parseType(item); }).join(' | ');
}
else if (type.type === 'TSIntersectionType') {
return type.types.map(function (item) { return parseType(item); }).join(' & ');
}
else if (type.type === 'TSLiteralType') {
return type.literal.type === 'StringLiteral' ? type.literal.value : type.literal.type;
// } else if (type.type === 'TSTypeLiteral') {
// return parseTypeLiteral(type);
}
else if (type.type === 'TSStringKeyword') {
return 'string';
}
else if (type.type === 'TSNumberKeyword') {
return 'number';
}
else if (type.type === 'TSBooleanKeyword') {
return 'boolean';
}
else {
return type.type;
}
}
function parseTypeLiteral(type) {
var items = [];
type.members.forEach(function (item) {
var _a, _b, _c;
if (item.type === 'TSPropertySignature') {
var key = item.key.type === 'Identifier' ? item.key.name : item.key.type;
var value = parseType((_a = item.typeAnnotation) === null || _a === void 0 ? void 0 : _a.typeAnnotation);
var comments = [
(_b = item.leadingComments) === null || _b === void 0 ? void 0 : _b.map(function (item) { return item.value.trim(); }).join('\n'),
(_c = item.trailingComments) === null || _c === void 0 ? void 0 : _c.map(function (item) { return item.value.trim(); }).join('\n')
].join('\n');
var required = item.optional ? false : true;
items.push({
type: value,
comment: comments,
key: key,
required: required
});
}
});
return items;
}
function getData(path) {
var _a, _b, _c;
var type = {};
if (path.isExportNamedDeclaration()) {
var comments = [
(_a = path.node.leadingComments) === null || _a === void 0 ? void 0 : _a.map(function (item) { return item.value.trim(); }).join('\n'),
(_b = path.node.trailingComments) === null || _b === void 0 ? void 0 : _b.map(function (item) { return item.value.trim(); }).join('\n')
].join('\n');
if (comments) {
type.comment = comments;
}
if (((_c = path.node.declaration) === null || _c === void 0 ? void 0 : _c.type) === 'TSTypeAliasDeclaration') {
if (path.node.declaration.id.type === 'Identifier') {
if (path.node.declaration.id.name.endsWith('Query')) {
type.type = 'query';
var queryType = path.node.declaration.typeAnnotation;
if (queryType) {
if (queryType.type === 'TSTypeLiteral') {
type.items = parseTypeLiteral(queryType);
}
else {
type.dataType = parseType(queryType);
}
}
}
else if (path.node.declaration.id.name.endsWith('Body')) {
type.type = 'body';
if (path.node.declaration.typeAnnotation) {
if (path.node.declaration.typeAnnotation.type === 'TSTypeLiteral') {
type.items = parseTypeLiteral(path.node.declaration.typeAnnotation);
}
else {
type.dataType = parseType(path.node.declaration.typeAnnotation);
}
}
}
else if (path.node.declaration.id.name.endsWith('Response')) {
type.type = 'response';
if (path.node.declaration.typeAnnotation) {
if (path.node.declaration.typeAnnotation.type === 'TSTypeLiteral') {
type.items = parseTypeLiteral(path.node.declaration.typeAnnotation);
}
else {
type.dataType = parseType(path.node.declaration.typeAnnotation);
}
}
}
else {
return;
}
}
}
}
return type;
}
function parseCode(code) {
var ast = (0, parser_1.parse)(code, {
sourceType: 'module',
plugins: ['typescript', 'jsx']
});
var api = {};
(0, traverse_1.default)(ast, {
enter: function (path) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
var metadata = getMetadata(path);
var description = getDescription(path);
var data = getData(path);
if (metadata) {
api.name = metadata.name;
api.author = metadata.author;
api.version = metadata.version;
}
if (description) {
api.description = description;
}
if (data) {
if (data.type === 'query') {
api.query = (_a = data.items) !== null && _a !== void 0 ? _a : {
type: (_b = data.dataType) !== null && _b !== void 0 ? _b : '',
comment: (_c = data.comment) !== null && _c !== void 0 ? _c : ''
};
}
else if (data.type === 'body') {
api.body = (_d = data.items) !== null && _d !== void 0 ? _d : {
type: (_e = data.dataType) !== null && _e !== void 0 ? _e : '',
comment: (_f = data.comment) !== null && _f !== void 0 ? _f : ''
};
}
else if (data.type === 'response') {
api.response = (_g = data.items) !== null && _g !== void 0 ? _g : {
type: (_h = data.dataType) !== null && _h !== void 0 ? _h : '',
comment: (_j = data.comment) !== null && _j !== void 0 ? _j : ''
};
}
}
}
});
return api;
}
function getMethod(api) {
if (api.query && !(Array.isArray(api.query) && api.query.length === 0)) {
return 'GET';
}
else if (api.body && !(Array.isArray(api.body) && api.body.length === 0)) {
return 'POST';
}
else {
return 'GET';
}
}
function parseAPI(_a) {
var path = _a.path, rootPath = _a.rootPath;
var code = fs.readFileSync(path, 'utf-8');
var api = parseCode(code);
api.url = path.replace('.ts', '').replace(rootPath, '');
api.path = path;
if (api.method === undefined) {
api.method = getMethod(api);
}
return api;
}