diff --git a/src/ghw_process_ota_files.ts b/src/ghw_process_ota_files.ts index a364c00..857b8c0 100644 --- a/src/ghw_process_ota_files.ts +++ b/src/ghw_process_ota_files.ts @@ -19,6 +19,10 @@ import { } from "./common.js"; import type {ExtraMetas, GHExtraMetas, RepoImageMeta} from "./types.js"; +const GLEDOPTO_MANUFACTURER_CODE = 4687; +const TUYA_MANUFACTURER_CODE_1 = 4098; +const TUYA_MANUFACTURER_CODE_2 = 4417; + const EXTRA_METAS_PR_BODY_START_TAG = "```json"; const EXTRA_METAS_PR_BODY_END_TAG = "```"; @@ -26,7 +30,6 @@ function getFileExtraMetas(extraMetas: GHExtraMetas, fileName: string): ExtraMet if (Array.isArray(extraMetas)) { const fileExtraMetas = extraMetas.find((m) => m.fileName === fileName) ?? {}; /** @see getValidMetas */ - // biome-ignore lint/performance/noDelete: delete fileExtraMetas.fileName; return fileExtraMetas; @@ -36,7 +39,7 @@ function getFileExtraMetas(extraMetas: GHExtraMetas, fileName: string): ExtraMet return extraMetas; } -async function getPRBody(github: Octokit, core: typeof CoreApi, context: Context): Promise { +async function getPRBody(github: Octokit, _core: typeof CoreApi, context: Context): Promise { assert(context.payload.pull_request || context.eventName === "push"); if (context.payload.pull_request) { @@ -119,7 +122,7 @@ export async function processOtaFiles( core.startGroup(filePath); const logPrefix = `[${filePath}]`; - let failureComment = ""; + let failureComment: string | undefined; try { const firmwareFileName = path.basename(filePath); @@ -145,6 +148,20 @@ export async function processOtaFiles( const [baseMatchIndex, baseMatch] = findMatchImage(parsedImage, baseManifest, fileExtraMetas); const statusToBase = getParsedImageStatus(parsedImage, baseMatch); + // Manufacturer specific checks + if (parsedImage.manufacturerCode === GLEDOPTO_MANUFACTURER_CODE && !fileExtraMetas.modelId) { + // Gledopto uses the same imageType for every device, force modelId to be present. + // https://github.com/Koenkk/zigbee-OTA/pull/864 + throw new Error("Gledopto image requires extra `modelId` metadata"); + } + if ( + (TUYA_MANUFACTURER_CODE_1 === parsedImage.manufacturerCode || TUYA_MANUFACTURER_CODE_2 === parsedImage.manufacturerCode) && + !fileExtraMetas.manufacturerName + ) { + // Tuya uses the same imageType for every device, force manufacturerName to be present. + throw new Error("Tuya image requires extra `manufacturerName` metadata"); + } + switch (statusToBase) { case ParsedImageStatus.Older: { // if prev doesn't have a match, move to prev diff --git a/tests/data.test.ts b/tests/data.test.ts index 5571fde..e8e8b9f 100644 --- a/tests/data.test.ts +++ b/tests/data.test.ts @@ -11,6 +11,8 @@ import {it} from "vitest"; import * as common from "../src/common"; import type {ExtraMetas, RepoImageMeta} from "../src/types"; +export const IMAGE_GLEDOPTO = "GL-B-008P_V17A1_OTAV7.ota"; +export const IMAGE_TUYA = "1662693814-oem_mg21_zg_nh_win_cover_relay_OTA_1.0.7.bin"; export const IMAGE_V14_1 = "ZLinky_router_v14.ota"; export const IMAGE_V14_2 = "ZLinky_router_v14_limited.ota"; export const IMAGE_V13_1 = "ZLinky_router_v13.ota"; diff --git a/tests/ghw_check_ota_pr.test.ts b/tests/ghw_check_ota_pr.test.ts index 659026b..e46fc52 100644 --- a/tests/ghw_check_ota_pr.test.ts +++ b/tests/ghw_check_ota_pr.test.ts @@ -10,7 +10,9 @@ import type {RepoImageMeta} from "../src/types"; import { BASE_IMAGES_TEST_DIR_PATH, getAdjustedContent, + IMAGE_GLEDOPTO, IMAGE_INVALID, + IMAGE_TUYA, IMAGE_V12_1, IMAGE_V12_1_METAS, IMAGE_V13_1, @@ -626,4 +628,28 @@ Text after end tag`); ]); expect(writeManifestSpy).toHaveBeenCalledWith(common.PREV_INDEX_MANIFEST_FILENAME, [IMAGE_V12_1_METAS]); }); + + it.each([ + ['"manufacturerName": ["_TZ3000"]', IMAGE_TUYA, undefined], + ["", IMAGE_TUYA, "Tuya image requires extra `manufacturerName` metadata"], + ['"modelId": "GL-C-008"', IMAGE_GLEDOPTO, undefined], + ["", IMAGE_GLEDOPTO, "Gledopto image requires extra `modelId` metadata"], + ])("manufacturer specific checks", async (body: string, image: string, error: string | undefined) => { + filePaths = [useImage(image)]; + const newContext = withBody(`\`\`\`json +{ + ${body} +} +\`\`\``); + + if (error === undefined) { + // @ts-expect-error mock + await checkOtaPR(github, core, newContext); + } else { + await expect(async () => { + // @ts-expect-error mock + await checkOtaPR(github, core, newContext); + }).rejects.toThrow(expect.objectContaining({message: expect.stringContaining(error)})); + } + }); }); diff --git a/tests/images/1662693814-oem_mg21_zg_nh_win_cover_relay_OTA_1.0.7.bin b/tests/images/1662693814-oem_mg21_zg_nh_win_cover_relay_OTA_1.0.7.bin new file mode 100755 index 0000000..446bdcd Binary files /dev/null and b/tests/images/1662693814-oem_mg21_zg_nh_win_cover_relay_OTA_1.0.7.bin differ diff --git a/tests/images/GL-B-008P_V17A1_OTAV7.ota b/tests/images/GL-B-008P_V17A1_OTAV7.ota new file mode 100644 index 0000000..6fc80d2 Binary files /dev/null and b/tests/images/GL-B-008P_V17A1_OTAV7.ota differ