From 9d247659c75d71a3fcdff172a694219be606826a Mon Sep 17 00:00:00 2001 From: Arian Kordi Date: Sun, 4 May 2025 15:25:57 -0400 Subject: [PATCH] Add Vitest, qr-codes.test.ts, and fix broken? import in tomodachi-life-mii.ts --- package.json | 6 ++- src/lib/qr-codes.test.ts | 71 +++++++++++++++++++++++++++++++++++ src/lib/tomodachi-life-mii.ts | 2 +- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/lib/qr-codes.test.ts diff --git a/package.json b/package.json index b362de2..92cf684 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build": "next build", "start": "next start", "lint": "next lint", - "postinstall": "prisma generate" + "postinstall": "prisma generate", + "test": "vitest" }, "dependencies": { "@2toad/profanity": "^3.1.1", @@ -48,6 +49,7 @@ "eslint-config-next": "15.2.4", "prisma": "^6.7.0", "tailwindcss": "^4.1.5", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^3.1.2" } } diff --git a/src/lib/qr-codes.test.ts b/src/lib/qr-codes.test.ts new file mode 100644 index 0000000..1936b6c --- /dev/null +++ b/src/lib/qr-codes.test.ts @@ -0,0 +1,71 @@ +import { convertQrCode } from "./qr-codes"; +import { describe, it, expect } from "vitest"; +import Mii from "./mii.js/mii"; +import TomodachiLifeMii from "./tomodachi-life-mii"; + +// List of encrypted QR code data to test against. +const validQrCodes = [ + // Frieren (from step6.webp) + { + base64: "lymEx6TA4eLgfwTEceW+DbdYBTfBfPKM4VesJkq1qzoGGXnk/3OohvPPuGS8mmnzvpvyC36ge8+C2c4IIhJ0l7Lx9KKxnPEAuLBM1YtCnSowjVNHo3CmjE7D7lkDUBuhO3qKjAqWXfQthtqBqjTe4Hv95TKNVPimaxNXhAVZGSmOMh++0Z2N0TvIDpU/8kxLIsgntsQH4PNlaFcVF2HeOERXRdTm/TMFb6pozO57nJ9NKi5bxh1ClNArbpyTQgBe7cfvnNretFVNWzGJBWctZC7weecYIKyU3qbH5c4kogmj4WcfJhYsuOT3odvv2WBaGhchgR779Ztc0E76COxNEaJa6M7QDyHGw8XQfxCH3j4pMkhFOlPn/ObS3rNUADYUCY8d+Wt4fBbO7PTW7ppDnDaCyxwEIbglsMtkD/cIPIr4f0RPnpV7ZlOmrJ3HdIbmwXi6TqKTwqkHtBmBPvZVpXm4RJN8A6gF22Uc7NY8B3KMYY7Q", + expected: { + miiName: "Frieren", + firstName: "Frieren", + lastName: "the Slayer", + islandName: "Wuhu", + }, + }, + { + base64: "ky1cf9hr9xotl250Y8cDOGqPd7t51NS8tNVrJMRAI2bfXr4LNirKvqu7ZrvC00vgz70NU8kQRR6H3uAnRaHupxjLbeYfU0s6CduruAEnXP8rZanCeSePQQH0NSL3QqiilT2WEt7nCAMvHwR9fT/LE/k6NBMDHqoK3zqzemr4OlQro0YWBeRJ501EawWan/k/rq2VSGTeLO09CsQD6AFHECtxF9+sSKyJxK1aiu7VhmOZLY6L9VKrlpvahQ1/vHVyYVpFJvc3bdHE8D94bBXkZ18mnXj++ST+j1Had4aki7oqTT83fgs7asRg3DRRZArw8PiKVmZWJ4ODRWR/LNvjIxb1FQqWk9I6S3DEo6AMuBRbXgj1H4YWrRuTkWlEpP2Y/P5+Mvfv5GbNQKYSKwpTYFOCTn13yQ1wtDbF4yG+Ro4Xf45cNBT6k3yqswrKt9bkP2wiULqYZR7McaD1SJ4whFFqcadjpLvbn8zQNFY0lOUTQMGI", + expected: { + miiName: "ミク", + firstName: "ミク", + lastName: "はつね", + islandName: "LOSTの", + }, + }, +]; + +const zeroes372 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + +const length32 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + +function base64ToUint8Array(base64: string): Uint8Array { + const binary = Buffer.from(base64, "base64"); + return new Uint8Array(binary.buffer, binary.byteOffset, binary.byteLength); +} + +describe("convertQrCode", () => { + it.each(validQrCodes.map((t, i) => [i, t]))( + "should convert valid QR #%i into Mii + TomodachiLifeMii", + (_, { base64, expected }) => { + const bytes = base64ToUint8Array(base64); + const { mii, tomodachiLifeMii } = convertQrCode(bytes); + + expect(mii).toBeInstanceOf(Mii); + expect(tomodachiLifeMii).toBeInstanceOf(TomodachiLifeMii); + + expect(mii.miiName).toBe(expected.miiName); + expect(tomodachiLifeMii.firstName).toBe(expected.firstName); + expect(tomodachiLifeMii.lastName).toBe(expected.lastName); + expect(tomodachiLifeMii.islandName).toBe(expected.islandName); + }); + + it("should throw an error on zeroed out data", () => { + const bytes = base64ToUint8Array(zeroes372); + expect(() => convertQrCode(bytes)).toThrow("QR code is not a valid Mii QR code"); + }); + + it("should throw an error on wrong length", () => { + const bytes = base64ToUint8Array(length32); + expect(() => convertQrCode(bytes)).toThrow("Mii data is not valid"); + }); + /* + it('should censor bad words in names', () => { + const qrWithSwears = // TODO TODO + const { tomodachiLifeMii } = convertQrCode(qrWithSwears); + + expect(tomodachiLifeMii.firstName).not.toMatch(/INSERT_SWEARS_HERE/i); + }); + */ +}); diff --git a/src/lib/tomodachi-life-mii.ts b/src/lib/tomodachi-life-mii.ts index 159434a..4d794ca 100644 --- a/src/lib/tomodachi-life-mii.ts +++ b/src/lib/tomodachi-life-mii.ts @@ -1,4 +1,4 @@ -import { TOMODACHI_LIFE_DECRYPTION_KEY } from "@/lib/constants"; +import { TOMODACHI_LIFE_DECRYPTION_KEY } from "../lib/constants"; import { AES_CTR } from "@trafficlunar/asmcrypto.js"; // Converts hair dye to studio color