From 2ed058795bf0ce968134aa8db18b0dd86b54c30e Mon Sep 17 00:00:00 2001 From: Seefs <40468931+seefs001@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:19:04 +0800 Subject: [PATCH] fix(ci): stabilize checks and configure Bun dependency updates (#7765) --- .github/dependabot.yml | 6 ++ .github/workflows/ci.yml | 6 +- controller/security_account_test.go | 5 ++ controller/security_enrollment_test.go | 13 +++- web/bun.lock | 37 ++-------- web/package.json | 7 +- .../components/ui/__tests__/markdown.test.tsx | 68 +++++++++++++++++++ .../__tests__/model-mapping-editor.test.tsx | 16 ++--- 8 files changed, 113 insertions(+), 45 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 web/src/components/ui/__tests__/markdown.test.tsx diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..92902fb1a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "bun" + directory: "/web" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca3407224..c20540c07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Checkout the event commit in both jobs. A PR merge ref can disappear while +# queued runs start, and a base branch can advance after the merge event. jobs: backend: name: Backend vet, build, and test @@ -25,7 +27,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: ${{ github.event.pull_request.base.repo.full_name }} - ref: ${{ github.event.action == 'closed' && github.event.pull_request.base.ref || github.ref }} + ref: ${{ github.sha }} - name: Set up Go uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 @@ -80,7 +82,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: ${{ github.event.pull_request.base.repo.full_name }} - ref: ${{ github.event.action == 'closed' && github.event.pull_request.base.ref || github.ref }} + ref: ${{ github.sha }} - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 diff --git a/controller/security_account_test.go b/controller/security_account_test.go index 37e29a24c..23a088286 100644 --- a/controller/security_account_test.go +++ b/controller/security_account_test.go @@ -234,6 +234,11 @@ func TestSecurityAccountDeletionConcurrentRequestsHaveOneWinner(t *testing.T) { require.NoError(t, model.DB.Unscoped().First(&deleted, user.Id).Error) assert.True(t, deleted.DeletedAt.Valid) assert.Equal(t, identity.UserAuthVersion+1, deleted.AuthVersion) + _, _, err := service.ValidateLoginSession(identity) + assert.Error(t, err) + count, err := model.CountActiveUserSessions(user.Id, time.Now().Unix()) + require.NoError(t, err) + assert.Zero(t, count) } type securityMailbox struct { diff --git a/controller/security_enrollment_test.go b/controller/security_enrollment_test.go index 3f44b4a7d..bb5a8e064 100644 --- a/controller/security_enrollment_test.go +++ b/controller/security_enrollment_test.go @@ -32,6 +32,7 @@ import ( "github.com/QuantumNous/new-api/setting/system_setting" "github.com/fxamacker/cbor/v2" "github.com/gin-gonic/gin" + "github.com/glebarez/sqlite" "github.com/golang-jwt/jwt/v5" "github.com/pquerna/otp/totp" "github.com/stretchr/testify/assert" @@ -54,7 +55,17 @@ func setupSecurityEnrollmentTest(t *testing.T) (*model.User, service.AuthIdentit dialect = "sqlite" } dsn := os.Getenv("TEST_" + strings.ToUpper(dialect) + "_DSN") - db, _ := newAuditTestDatabase(t, dialect, dsn) + var db *gorm.DB + if dialect == "sqlite" { + // Match common.SQLitePath: take the writer lock before transaction reads. + // Deferred transactions can fail to upgrade their read lock when concurrent + // proof consumption overlaps account deletion or session revocation. + var err error + db, err = gorm.Open(sqlite.Open(t.TempDir()+"/security.db?_pragma=busy_timeout(30000)&_pragma=journal_mode(WAL)&_txlock=immediate"), &gorm.Config{}) + require.NoError(t, err) + } else { + db, _ = newAuditTestDatabase(t, dialect, dsn) + } logDB, _ := newAuditTestDatabase(t, dialect, dsn) db.Logger = logger.Default.LogMode(logger.Silent) logDB.Logger = logger.Default.LogMode(logger.Silent) diff --git a/web/bun.lock b/web/bun.lock index 22f73e5b8..d8cb685c8 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -31,11 +31,11 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dayjs": "^1.11.21", - "dompurify": "3.4.13", + "dompurify": "3.4.16", "i18next": "^26.3.4", "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.4.2", - "katex": "^0.17.0", + "katex": "^0.18.2", "lucide-react": "^1.23.0", "marked": "^18.0.5", "motion": "^12.42.2", @@ -95,11 +95,12 @@ }, "overrides": { "brace-expansion": "2.1.1", - "dompurify": "3.4.13", + "dompurify": "3.4.16", "fast-uri": "3.1.2", "hono": "4.12.22", "ip-address": "10.2.0", "js-cookie": "3.0.7", + "katex": "0.18.2", "mermaid": "11.15.0", "minimist": "1.2.8", "postcss": "8.5.15", @@ -1011,10 +1012,6 @@ "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], - "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], - - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260707.2", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260707.2", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260707.2", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260707.2", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260707.2", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260707.2", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260707.2", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260707.2" }, "bin": { "tsgo": "bin/tsgo" } }, "sha512-oUGp+Rep/hqMhPunyinsALUwSlzHINSxitifPiSaeqoKOKD2OlR9NE3TaPqwsl4NlGslsOSUXI1JotWQzpYCPg=="], "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260707.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wny2pgKjGbiZtnOIHVa3tXC1UfDqxNEFzyPGmiqybedG8hipG2Nfp0l5UxbaKCjkLacUpH/W5bP2hBOMVhCOzg=="], @@ -1165,8 +1162,6 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], - "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -1385,7 +1380,7 @@ "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], - "dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="], + "dompurify": ["dompurify@3.4.16", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ=="], "dot-prop": ["dot-prop@6.0.1", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA=="], @@ -1569,8 +1564,6 @@ "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], - "happy-dom": ["happy-dom@20.11.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg=="], - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -1741,7 +1734,7 @@ "jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], - "katex": ["katex@0.17.0", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw=="], + "katex": ["katex@0.18.2", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-3snve4y0SXTMequLim1FMiPvLGElySam0blN5xQZBqEY3lOJz1TnuaaiugnBZBqHzlSAGmFTYL7MCQkORuLKCA=="], "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], @@ -2583,8 +2576,6 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], @@ -2733,10 +2724,6 @@ "geojson-dissolve/@turf/meta": ["@turf/meta@3.14.0", "", {}, "sha512-OtXqLQuR9hlQ/HkAF/OdzRea7E0eZK1ay8y8CBXkoO2R6v34CsDrWYLMSo0ZzMsaQDpKo76NPP2GGo+PyG1cSg=="], - "happy-dom/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - - "happy-dom/whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], - "hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -2757,12 +2744,8 @@ "mermaid/es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="], - "mermaid/katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], - "mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], - "micromark-extension-math/katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], - "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -2785,8 +2768,6 @@ "recharts/es-toolkit": ["es-toolkit@1.49.0", "", {}, "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g=="], - "rehype-katex/katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], - "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], @@ -2853,12 +2834,6 @@ "hast-util-raw/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "mermaid/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "micromark-extension-math/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "rehype-katex/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "simplify-geojson/concat-stream/readable-stream": ["readable-stream@1.1.14", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ=="], diff --git a/web/package.json b/web/package.json index 34ac9bb7d..a21d106c9 100644 --- a/web/package.json +++ b/web/package.json @@ -50,11 +50,11 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "dayjs": "^1.11.21", - "dompurify": "3.4.13", + "dompurify": "3.4.16", "i18next": "^26.3.4", "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.4.2", - "katex": "^0.17.0", + "katex": "^0.18.2", "lucide-react": "^1.23.0", "marked": "^18.0.5", "motion": "^12.42.2", @@ -112,11 +112,12 @@ }, "overrides": { "brace-expansion": "2.1.1", - "dompurify": "3.4.13", + "dompurify": "3.4.16", "fast-uri": "3.1.2", "hono": "4.12.22", "ip-address": "10.2.0", "js-cookie": "3.0.7", + "katex": "0.18.2", "mermaid": "11.15.0", "minimist": "1.2.8", "postcss": "8.5.15", diff --git a/web/src/components/ui/__tests__/markdown.test.tsx b/web/src/components/ui/__tests__/markdown.test.tsx new file mode 100644 index 000000000..6d4171ae1 --- /dev/null +++ b/web/src/components/ui/__tests__/markdown.test.tsx @@ -0,0 +1,68 @@ +/* +Copyright (C) 2023-2026 QuantumNous + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +For commercial licensing, please contact support@quantumnous.com +*/ +import { render } from '@testing-library/react' +import { expect, test } from 'vitest' + +import { Markdown } from '../markdown' + +test.each([ + ['inline', 'Formula: $$\\frac{1}{2}$$.'], + ['display', '$$\n\\frac{1}{2}\n$$'], + ['fenced', '```math\n\\frac{1}{2}\n```'], +])('preserves MathML and visual output when rendering %s math', (_, source) => { + const view = render({source}) + const math = view.getByRole('math', { hidden: true }) + + expect(math.querySelector('mfrac')).not.toBeNull() + expect(math.querySelector('annotation')).toHaveTextContent('\\frac{1}{2}') + expect(view.container.querySelector('.katex-html')).toHaveAttribute( + 'aria-hidden', + 'true' + ) +}) + +test('does not load math resources when trust is inherited from a polluted prototype', () => { + const originalTrust = Object.getOwnPropertyDescriptor( + Object.prototype, + 'trust' + ) + + try { + Object.defineProperty(Object.prototype, 'trust', { + configurable: true, + value: true, + writable: true, + }) + + const view = render( + + {'$$\\includegraphics{https://example.com/untrusted.png}$$'} + + ) + + expect(view.container.querySelector('img')).toBeNull() + expect(view.container).toHaveTextContent('\\includegraphics') + } finally { + if (originalTrust) { + Object.defineProperty(Object.prototype, 'trust', originalTrust) + } else { + Reflect.deleteProperty(Object.prototype, 'trust') + } + } +}) diff --git a/web/src/features/channels/components/__tests__/model-mapping-editor.test.tsx b/web/src/features/channels/components/__tests__/model-mapping-editor.test.tsx index 907e15b96..2c4ab8965 100644 --- a/web/src/features/channels/components/__tests__/model-mapping-editor.test.tsx +++ b/web/src/features/channels/components/__tests__/model-mapping-editor.test.tsx @@ -190,15 +190,15 @@ test('a draft request with only the upstream name focuses the request field, and draftRequest={{ from: '', to: 'up-1', token: 1 }} /> ) - await waitFor(() => - expect( - screen.getAllByRole('combobox', { name: 'Request Model Name' }) - ).toHaveLength(2) - ) + // The row is committed before its passive effect moves focus. + await waitFor(() => { + const requestFields = screen.getAllByRole('combobox', { + name: 'Request Model Name', + }) + expect(requestFields).toHaveLength(2) + expect(requestFields[1]).toHaveFocus() + }) expect(screen.getByDisplayValue('up-1')).toBeVisible() - expect( - screen.getAllByRole('combobox', { name: 'Request Model Name' })[1] - ).toHaveFocus() view.rerender(