From a16a408224716776dcddbc54314711488b65d0ca Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 11 Apr 2021 16:58:56 +0200 Subject: [PATCH 1/6] Strongly type WebsocketBlobResult --- tnoodle-ui/src/main/api/tnoodle.api.ts | 6 ++---- tnoodle-ui/src/main/api/tnoodle.socket.ts | 18 ++++++++---------- tnoodle-ui/src/main/components/Main.tsx | 3 ++- .../src/main/model/WebsocketBlobResult.ts | 4 ++++ .../src/main/redux/slice/ScramblingSlice.ts | 7 +++---- 5 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 tnoodle-ui/src/main/model/WebsocketBlobResult.ts diff --git a/tnoodle-ui/src/main/api/tnoodle.api.ts b/tnoodle-ui/src/main/api/tnoodle.api.ts index 143e65afa..dce0d8b2a 100644 --- a/tnoodle-ui/src/main/api/tnoodle.api.ts +++ b/tnoodle-ui/src/main/api/tnoodle.api.ts @@ -6,6 +6,7 @@ import WcaEvent from "../model/WcaEvent"; import WcaFormat from "../model/WcaFormat"; import Wcif from "../model/Wcif"; import { ScrambleClient } from "./tnoodle.socket"; +import WebsocketBlobResult from "../model/WebsocketBlobResult"; let backendUrl = new URL("http://localhost:2014"); export const tNoodleBackend = backendUrl.toString().replace(/\/$/g, ""); @@ -74,10 +75,7 @@ class TnoodleApi { return scrambleClient.loadScrambles(zipEndpoint, payload, wcif.id); }; - convertToBlob = async (result: { - contentType: string; - payload: string; - }) => { + convertToBlob = async (result: WebsocketBlobResult) => { let { contentType, payload } = result; let res = await fetch(`data:${contentType};base64,${payload}`); diff --git a/tnoodle-ui/src/main/api/tnoodle.socket.ts b/tnoodle-ui/src/main/api/tnoodle.socket.ts index 1e25e1308..071cf6e36 100644 --- a/tnoodle-ui/src/main/api/tnoodle.socket.ts +++ b/tnoodle-ui/src/main/api/tnoodle.socket.ts @@ -1,10 +1,9 @@ import { tNoodleBackend } from "./tnoodle.api"; +import WebsocketBlobResult from "../model/WebsocketBlobResult"; export type ScrambleHandshakeFn = (payload: Record) => void; export type ScrambleProgressFn = (payload: string) => void; -export type ScramblingBlobResult = any & { contentType: string; payload: any }; - enum ScramblingState { Idle, Initiate, @@ -21,10 +20,10 @@ export class ScrambleClient { state: ScramblingState; - contentType: string | null; + contentType: string; - resultPayload: object | null; - errorPayload: object | null; + resultPayload: string; + errorPayload: any; constructor( onHandshake: ScrambleHandshakeFn, @@ -35,9 +34,9 @@ export class ScrambleClient { this.state = ScramblingState.Idle; - this.contentType = null; + this.contentType = FALLBACK_APPLICATION_TYPE; - this.resultPayload = null; + this.resultPayload = ""; this.errorPayload = null; } @@ -45,7 +44,7 @@ export class ScrambleClient { endpoint: String, payload: object, targetMarker: String - ): Promise { + ): Promise { return new Promise((resolve, reject) => { let ws = new WebSocket(BASE_URL + endpoint); @@ -61,8 +60,7 @@ export class ScrambleClient { ws.onclose = (cls) => { if (this.state === ScramblingState.Done && cls.wasClean) { let resultObject = { - contentType: - this.contentType ?? FALLBACK_APPLICATION_TYPE, + contentType: this.contentType, payload: this.resultPayload, }; diff --git a/tnoodle-ui/src/main/components/Main.tsx b/tnoodle-ui/src/main/components/Main.tsx index 39307814b..73e4a3c09 100644 --- a/tnoodle-ui/src/main/components/Main.tsx +++ b/tnoodle-ui/src/main/components/Main.tsx @@ -16,6 +16,7 @@ import EventPickerTable from "./EventPickerTable"; import Interceptor from "./Interceptor"; import "./Main.css"; import VersionInfo from "./VersionInfo"; +import WebsocketBlobResult from "../model/WebsocketBlobResult"; const Main = () => { const [competitionNameFileZip, setCompetitionNameFileZip] = useState(""); @@ -74,7 +75,7 @@ const Main = () => { tnoodleApi .fetchZip(scrambleClient, wcif, mbld, password, translations) - .then((plainZip: { contentType: string; payload: string }) => + .then((plainZip: WebsocketBlobResult) => dispatch(setFileZip(plainZip)) ) .catch((err: any) => interceptorRef.current?.updateMessage(err)) diff --git a/tnoodle-ui/src/main/model/WebsocketBlobResult.ts b/tnoodle-ui/src/main/model/WebsocketBlobResult.ts new file mode 100644 index 000000000..8415199d4 --- /dev/null +++ b/tnoodle-ui/src/main/model/WebsocketBlobResult.ts @@ -0,0 +1,4 @@ +export default interface WebsocketBlobResult { + contentType: string; + payload: string; +} diff --git a/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts b/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts index eb7476f57..93fab23cd 100644 --- a/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts +++ b/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts @@ -1,7 +1,8 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import WebsocketBlobResult from "../../model/WebsocketBlobResult"; interface ScramblingState { - fileZip?: { contentType: string; payload: string }; + fileZip?: WebsocketBlobResult; generatingScrambles: boolean; officialZipStatus: boolean; password: string; @@ -24,9 +25,7 @@ export const scramblingSlice = createSlice({ reducers: { setFileZip: ( state, - action: PayloadAction< - { contentType: string; payload: string } | undefined - > + action: PayloadAction ) => { state.fileZip = action.payload; }, From 2027e3d41633aa798e3a652aa865566195e49f3c Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 11 Apr 2021 17:01:29 +0200 Subject: [PATCH 2/6] Add linting tasks to package.json --- tnoodle-ui/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tnoodle-ui/package.json b/tnoodle-ui/package.json index c3860193b..25e564553 100644 --- a/tnoodle-ui/package.json +++ b/tnoodle-ui/package.json @@ -30,7 +30,8 @@ "build": "react-scripts build", "test": "react-scripts test --watchAll --watchAll=false --coverage", "eject": "react-scripts eject", - "prettier": "prettier --check ." + "prettier": "prettier --check .", + "lint": "prettier --write ." }, "eslintConfig": { "extends": [ From 3311be6bc2c9c566407f129e895b249e7835a678 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 11 Apr 2021 17:04:57 +0200 Subject: [PATCH 3/6] Change FrontendStatus information --- .../tnoodle/server/webscrambles/wcif/WCIFDataBuilder.kt | 8 ++++---- .../wcif/model/extension/ExtensionBuilders.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/WCIFDataBuilder.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/WCIFDataBuilder.kt index e1b2609d4..3ea074541 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/WCIFDataBuilder.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/WCIFDataBuilder.kt @@ -86,12 +86,12 @@ object WCIFDataBuilder { } fun TNoodleStatusExtension.pickWatermarkPhrase(): String? { - return if (!isOfficialBuild) { + return if (isStaging) { + WATERMARK_STAGING + } else if (!isSignedBuild) { WATERMARK_UNOFFICIAL - } else if (!isRecentVersion) { + } else if (!isAllowedVersion) { WATERMARK_OUTDATED - } else if (isStaging) { - WATERMARK_STAGING } else null } diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/extension/ExtensionBuilders.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/extension/ExtensionBuilders.kt index 8e66d2fc0..03a37142a 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/extension/ExtensionBuilders.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/wcif/model/extension/ExtensionBuilders.kt @@ -105,7 +105,7 @@ data class SheetCopyCountExtension(val numCopies: Int) : ExtensionBuilder() { @Serializable @SerialName(TNoodleStatusExtension.ID) -data class TNoodleStatusExtension(val isStaging: Boolean, val isManual: Boolean, val isOfficialBuild: Boolean, val isRecentVersion: Boolean) : ExtensionBuilder() { +data class TNoodleStatusExtension(val isStaging: Boolean, val isManual: Boolean, val isSignedBuild: Boolean, val isAllowedVersion: Boolean) : ExtensionBuilder() { override val id get() = ID override val specUrl get() = SPEC_URL From 7ced866eb25bd15a8b36155825248c44bc683338 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 11 Apr 2021 17:23:17 +0200 Subject: [PATCH 4/6] Transmit frontend status information when requesting ZIP --- tnoodle-ui/src/main/api/tnoodle.api.ts | 3 +++ tnoodle-ui/src/main/components/Main.tsx | 25 ++++++++++++++++--- .../src/main/components/VersionInfo.tsx | 15 +++++------ tnoodle-ui/src/main/model/FrontendStatus.ts | 6 +++++ .../src/main/redux/slice/ScramblingSlice.ts | 16 ++++++++---- 5 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 tnoodle-ui/src/main/model/FrontendStatus.ts diff --git a/tnoodle-ui/src/main/api/tnoodle.api.ts b/tnoodle-ui/src/main/api/tnoodle.api.ts index dce0d8b2a..d64b8bea9 100644 --- a/tnoodle-ui/src/main/api/tnoodle.api.ts +++ b/tnoodle-ui/src/main/api/tnoodle.api.ts @@ -7,6 +7,7 @@ import WcaFormat from "../model/WcaFormat"; import Wcif from "../model/Wcif"; import { ScrambleClient } from "./tnoodle.socket"; import WebsocketBlobResult from "../model/WebsocketBlobResult"; +import FrontendStatus from "../model/FrontendStatus"; let backendUrl = new URL("http://localhost:2014"); export const tNoodleBackend = backendUrl.toString().replace(/\/$/g, ""); @@ -63,6 +64,7 @@ class TnoodleApi { wcif: Wcif, mbld: string, password: string, + status: FrontendStatus, translations?: Translation[] ) => { let payload = { @@ -70,6 +72,7 @@ class TnoodleApi { multiCubes: { requestedScrambles: mbld }, fmcLanguages: fmcTranslationsHelper(translations), zipPassword: !password ? null : password, + frontendStatus: status, }; return scrambleClient.loadScrambles(zipEndpoint, payload, wcif.id); diff --git a/tnoodle-ui/src/main/components/Main.tsx b/tnoodle-ui/src/main/components/Main.tsx index 73e4a3c09..fdc8ef683 100644 --- a/tnoodle-ui/src/main/components/Main.tsx +++ b/tnoodle-ui/src/main/components/Main.tsx @@ -34,8 +34,11 @@ const Main = () => { const generatingScrambles = useSelector( (state: RootState) => state.scramblingSlice.generatingScrambles ); - const officialZipStatus = useSelector( - (state: RootState) => state.scramblingSlice.officialZipStatus + const isValidSignedBuild = useSelector( + (state: RootState) => state.scramblingSlice.isValidSignedBuild + ); + const isAllowedVersion = useSelector( + (state: RootState) => state.scramblingSlice.isAllowedVersion ); const fileZip = useSelector( (state: RootState) => state.scramblingSlice.fileZip @@ -73,8 +76,22 @@ const Main = () => { onScrambleProgress ); + let frontendStatus = { + isStaging: isUsingStaging(), + isManual: competitionId == null, + isSignedBuild: isValidSignedBuild, + isAllowedVersion: isAllowedVersion, + }; + tnoodleApi - .fetchZip(scrambleClient, wcif, mbld, password, translations) + .fetchZip( + scrambleClient, + wcif, + mbld, + password, + frontendStatus, + translations + ) .then((plainZip: WebsocketBlobResult) => dispatch(setFileZip(plainZip)) ) @@ -91,6 +108,8 @@ const Main = () => { // If TNoodle version is not official (as per VersionInfo) or if we generate scrambles using // a competition from staging, add a [Unofficial] + let officialZipStatus = isValidSignedBuild && isAllowedVersion; + let isUnofficialZip = !officialZipStatus || (competitionId != null && isUsingStaging()); diff --git a/tnoodle-ui/src/main/components/VersionInfo.tsx b/tnoodle-ui/src/main/components/VersionInfo.tsx index 0334e99bd..f74c42291 100644 --- a/tnoodle-ui/src/main/components/VersionInfo.tsx +++ b/tnoodle-ui/src/main/components/VersionInfo.tsx @@ -3,7 +3,10 @@ import { useDispatch } from "react-redux"; import tnoodleApi from "../api/tnoodle.api"; import wcaApi from "../api/wca.api"; import CurrentTnoodle from "../model/CurrentTnoodle"; -import { setOfficialZipStatus } from "../redux/slice/ScramblingSlice"; +import { + setAllowedVersion, + setValidSignedBuild, +} from "../redux/slice/ScramblingSlice"; const VersionInfo = () => { const [currentTnoodle, setCurrentTnoodle] = useState(); @@ -46,20 +49,18 @@ const VersionInfo = () => { }, [dispatch]); // This avoids global state update while rendering - const analyzeVerion = () => { + const analyzeVersion = () => { // We wait until both wca and tnoodle answers if (!allowedTnoodleVersions || !runningVersion) { return; } dispatch( - setOfficialZipStatus( - signatureValid && - allowedTnoodleVersions.includes(runningVersion) - ) + setAllowedVersion(allowedTnoodleVersions.includes(runningVersion)) ); + dispatch(setValidSignedBuild(signatureValid)); }; - useEffect(analyzeVerion, [ + useEffect(analyzeVersion, [ allowedTnoodleVersions, dispatch, runningVersion, diff --git a/tnoodle-ui/src/main/model/FrontendStatus.ts b/tnoodle-ui/src/main/model/FrontendStatus.ts new file mode 100644 index 000000000..92eb3f99e --- /dev/null +++ b/tnoodle-ui/src/main/model/FrontendStatus.ts @@ -0,0 +1,6 @@ +export default interface FrontendStatus { + isStaging: boolean; + isManual: boolean; + isSignedBuild: boolean; + isAllowedVersion: boolean; +} diff --git a/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts b/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts index 93fab23cd..9229628bc 100644 --- a/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts +++ b/tnoodle-ui/src/main/redux/slice/ScramblingSlice.ts @@ -4,7 +4,8 @@ import WebsocketBlobResult from "../../model/WebsocketBlobResult"; interface ScramblingState { fileZip?: WebsocketBlobResult; generatingScrambles: boolean; - officialZipStatus: boolean; + isValidSignedBuild: boolean; + isAllowedVersion: boolean; password: string; scramblingProgressCurrent: Record; scramblingProgressTarget: Record; @@ -13,8 +14,9 @@ interface ScramblingState { const initialState: ScramblingState = { fileZip: undefined, generatingScrambles: false, + isValidSignedBuild: false, + isAllowedVersion: false, password: "", - officialZipStatus: true, scramblingProgressCurrent: {}, scramblingProgressTarget: {}, }; @@ -32,8 +34,11 @@ export const scramblingSlice = createSlice({ setGeneratingScrambles: (state, action: PayloadAction) => { state.generatingScrambles = action.payload; }, - setOfficialZipStatus: (state, action: PayloadAction) => { - state.officialZipStatus = action.payload; + setValidSignedBuild: (state, action: PayloadAction) => { + state.isValidSignedBuild = action.payload; + }, + setAllowedVersion: (state, action: PayloadAction) => { + state.isAllowedVersion = action.payload; }, setPassword: (state, action: PayloadAction) => { state.password = action.payload; @@ -59,7 +64,8 @@ export const scramblingSlice = createSlice({ export const { setFileZip, - setOfficialZipStatus, + setValidSignedBuild, + setAllowedVersion, setPassword, setGeneratingScrambles, resetScramblingProgressCurrent, From a57d58addda945ac194778efd3697a9259d50f53 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 11 Apr 2021 17:37:19 +0200 Subject: [PATCH 5/6] Make PDF watermark more subtle --- .../webscrambles/pdf/WatermarkPdfWrapper.kt | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/WatermarkPdfWrapper.kt b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/WatermarkPdfWrapper.kt index 81fbc5db7..d839f0dd6 100644 --- a/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/WatermarkPdfWrapper.kt +++ b/webscrambles/src/main/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/pdf/WatermarkPdfWrapper.kt @@ -28,9 +28,30 @@ class WatermarkPdfWrapper( val pr = PdfReader(original.render()) for (pageN in 1..pr.numberOfPages) { - val page = getImportedPage(pr, pageN) - document.newPage() + + // Frontend watermark + if (watermark != null) { + val transparentState = PdfGState().apply { + setFillOpacity(WATERMARK_OPACITY) + setStrokeOpacity(WATERMARK_OPACITY) + } + + cb.saveState() + cb.setGState(transparentState) + + val diagRotation = atan(PAGE_SIZE.height / PAGE_SIZE.width) * (180f / PI) + + ColumnText.showTextAligned(cb, + Element.ALIGN_CENTER, Phrase(watermark, Font(FontUtil.NOTO_SANS_FONT, 72f, Font.BOLD)), + (PAGE_SIZE.left + PAGE_SIZE.right) / 2, (PAGE_SIZE.top + PAGE_SIZE.bottom) / 2, diagRotation.toFloat()) + + cb.restoreState() + } + + // add the imported page *after* potential watermarks + // so the watermark stays in the background + val page = getImportedPage(pr, pageN) cb.addTemplate(page, 0f, 0f) val rect = pr.getBoxSize(pageN, "art") @@ -64,28 +85,11 @@ class WatermarkPdfWrapper( ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, Phrase(generatedBy), (PAGE_SIZE.left + PAGE_SIZE.right) / 2, footerRect.top - footerRect.height / 4, 0f) - - // Staging watermark - if (watermark != null) { - val transparentState = PdfGState().apply { - setFillOpacity(0.2f) - } - - cb.saveState() - cb.setGState(transparentState) - - val diagRotation = atan(PAGE_SIZE.height / PAGE_SIZE.width) * (180f / PI) - - ColumnText.showTextAligned(cb, - Element.ALIGN_CENTER, Phrase(watermark, Font(FontUtil.NOTO_SANS_FONT, 100f, Font.BOLD)), - (PAGE_SIZE.left + PAGE_SIZE.right) / 2, (PAGE_SIZE.top + PAGE_SIZE.bottom) / 2, diagRotation.toFloat()) - - cb.restoreState() - } } } companion object { const val HEADER_AND_FOOTER_HEIGHT_RATIO = 12 + const val WATERMARK_OPACITY = 0.1f } } From 6af2b70f869da907ec07769ce493c186569392cb Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 11 Apr 2021 18:03:38 +0200 Subject: [PATCH 6/6] Fix tnoodle-ui tests --- tnoodle-ui/build.gradle.kts | 2 +- tnoodle-ui/src/test/App.test.tsx | 16 +++++++--------- .../src/test/mock/tnoodle.api.test.mock.ts | 7 +++++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/tnoodle-ui/build.gradle.kts b/tnoodle-ui/build.gradle.kts index 98719c69d..e20c8f1b9 100644 --- a/tnoodle-ui/build.gradle.kts +++ b/tnoodle-ui/build.gradle.kts @@ -22,7 +22,7 @@ val yarnInstall = tasks.named("yarn_install") { val yarnBuild = tasks.named("yarn_build") { dependsOn(yarnInstall) - inputs.files(fileTree("src").exclude("*.css")) + inputs.files(fileTree("src/main").exclude("*.css")) inputs.dir("public") inputs.file("package.json") diff --git a/tnoodle-ui/src/test/App.test.tsx b/tnoodle-ui/src/test/App.test.tsx index a90b6fabe..435da4dc4 100644 --- a/tnoodle-ui/src/test/App.test.tsx +++ b/tnoodle-ui/src/test/App.test.tsx @@ -1,4 +1,3 @@ -import { configureStore } from "@reduxjs/toolkit"; import { fireEvent } from "@testing-library/react"; import { shuffle } from "lodash"; import React from "react"; @@ -10,15 +9,10 @@ import tnoodleApi from "../main/api/tnoodle.api"; import wcaApi from "../main/api/wca.api"; import Translation from "../main/model/Translation"; import Wcif from "../main/model/Wcif"; -import { competitionSlice } from "../main/redux/slice/CompetitionSlice"; -import { fmcSlice } from "../main/redux/slice/FmcSlice"; -import { informationSlice } from "../main/redux/slice/InformationSlice"; -import { mbldSlice } from "../main/redux/slice/MbldSlice"; -import { scramblingSlice } from "../main/redux/slice/ScramblingSlice"; -import { wcifSlice } from "../main/redux/slice/WcifSlice"; import { defaultWcif } from "../main/util/wcif.util"; import { bestMbldAttempt, + defaultStatus, events, formats, languages, @@ -32,12 +26,14 @@ import { scrambleProgram, wcifs, } from "./mock/wca.api.test.mock"; +import FrontendStatus from "../main/model/FrontendStatus"; let container = document.createElement("div"); let wcif: Wcif | null = null; let mbld: string | null = null; let password: string | null = null; +let status: FrontendStatus | null = null; let translations: Translation[] | undefined; beforeEach(() => { // setup a DOM element as a render target @@ -68,13 +64,14 @@ beforeEach(() => { ); jest.spyOn(tnoodleApi, "fetchZip").mockImplementation( - (scrambleClient, _wcif, _mbld, _password, _translations) => { + (scrambleClient, _wcif, _mbld, _password, _status, _translations) => { wcif = _wcif; mbld = _mbld; password = _password; + status = _status; translations = _translations; - return Promise.resolve({ ...axiosResponse, data: plainZip }); + return Promise.resolve(plainZip); } ); @@ -147,6 +144,7 @@ it("Just generate scrambles", async () => { expect(wcif!.events.length).toBe(1); expect(password).toBe(""); + expect(status).toEqual(defaultStatus); }); it("Changes on 333, scramble", async () => { diff --git a/tnoodle-ui/src/test/mock/tnoodle.api.test.mock.ts b/tnoodle-ui/src/test/mock/tnoodle.api.test.mock.ts index fa7d41ae2..eee69ce83 100644 --- a/tnoodle-ui/src/test/mock/tnoodle.api.test.mock.ts +++ b/tnoodle-ui/src/test/mock/tnoodle.api.test.mock.ts @@ -199,6 +199,13 @@ export const plainZip = { payload: "UEsDBBQACAgIAK...", }; +export const defaultStatus = { + isStaging: false, + isManual: true, + isSignedBuild: true, + isAllowedVersion: true, +}; + export const bestMbldAttempt = { solved: 60, attempted: 60,