mirror of
https://github.com/YuzuZensai/beamboxctl.git
synced 2026-07-21 20:42:19 +00:00
339 lines
12 KiB
TypeScript
339 lines
12 KiB
TypeScript
import { describe, test, expect } from "bun:test";
|
|
import { ResponseParser } from "./response-parser.ts";
|
|
import { PacketType } from "../packet-types.ts";
|
|
import { ResponseStatus } from "../response-types.ts";
|
|
import { hexToBuffer } from "../../../__tests__/utils/test-helpers.ts";
|
|
|
|
const knownPackets = {
|
|
getPacketSuccess: { text: "GetPacketSuccess" },
|
|
getPacketFail: { text: "GetPacketFail" },
|
|
errorResponse: { text: "1111111111" },
|
|
deviceStatusResponse: {
|
|
hex: "7b2274797065223a31332c22616c6c7370616365223a31363338342c22667265657370616365223a31333839322c226465766e616d65223a224265616d426f78222c2273697a65223a223634783332222c226272616e64223a317d",
|
|
json: {
|
|
type: 13,
|
|
allspace: 16384,
|
|
freespace: 13892,
|
|
devname: "BeamBox",
|
|
size: "64x32",
|
|
brand: 1
|
|
}
|
|
}
|
|
};
|
|
|
|
describe("ResponseParser", () => {
|
|
describe("parse() - basic parsing", () => {
|
|
test("empty buffer returns empty ParsedResponse", () => {
|
|
const result = ResponseParser.parse(Buffer.alloc(0));
|
|
expect(result.rawText).toBe("");
|
|
expect(result.status).toBeNull();
|
|
expect(result.jsonData).toBeNull();
|
|
expect(result.isStatus).toBe(false);
|
|
});
|
|
|
|
test("cleans null bytes (\\x00)", () => {
|
|
const data = Buffer.from("\x00hello\x00world\x00");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.rawText).toBe("helloworld");
|
|
});
|
|
|
|
test("cleans \\xD1 bytes", () => {
|
|
const data = Buffer.from([0xd1, 0x68, 0x69, 0xd1]); // \xD1hi\xD1
|
|
const result = ResponseParser.parse(data);
|
|
// Note: \xD1 might not be cleaned by current implementation
|
|
expect(result.rawText.includes("hi")).toBe(true);
|
|
});
|
|
|
|
test("trims whitespace", () => {
|
|
const data = Buffer.from(" test ");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.rawText).toBe("test");
|
|
});
|
|
|
|
test("combines cleaning and trimming", () => {
|
|
const data = Buffer.from("\x00 \xD1 test \xD1 \x00");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.rawText).toBe("test");
|
|
});
|
|
});
|
|
|
|
describe("parse() - status detection", () => {
|
|
test("'GetPacketSuccess' sets status to SUCCESS", () => {
|
|
const data = Buffer.from("GetPacketSuccess");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.status).toBe(ResponseStatus.SUCCESS);
|
|
});
|
|
|
|
test("'GetPacketFail' sets status to FAIL", () => {
|
|
const data = Buffer.from("GetPacketFail");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.status).toBe(ResponseStatus.FAIL);
|
|
});
|
|
|
|
test("'1111111111' sets status to ERROR", () => {
|
|
const data = Buffer.from("1111111111");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.status).toBe(ResponseStatus.ERROR);
|
|
});
|
|
|
|
test("no status text returns null status", () => {
|
|
const data = Buffer.from("random text");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.status).toBeNull();
|
|
});
|
|
|
|
test("status detection with null bytes", () => {
|
|
const data = Buffer.from("\x00GetPacketSuccess\x00");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.status).toBe(ResponseStatus.SUCCESS);
|
|
});
|
|
});
|
|
|
|
describe("parse() - JSON extraction", () => {
|
|
test("valid JSON string", () => {
|
|
const data = Buffer.from('{"type":13,"value":42}');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toEqual({ type: 13, value: 42 });
|
|
});
|
|
|
|
test("JSON with null bytes", () => {
|
|
const data = Buffer.from('\x00{"type":13}\x00');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toEqual({ type: 13 });
|
|
});
|
|
|
|
test("JSON with prefix/suffix", () => {
|
|
const data = Buffer.from('abc{"type":13}xyz');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toEqual({ type: 13 });
|
|
});
|
|
|
|
test("JSON with \\xD1 bytes", () => {
|
|
const data = Buffer.from([0xd1, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x7d, 0xd1]);
|
|
// \xD1{"type":13}\xD1
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toEqual({ type: 13 });
|
|
});
|
|
|
|
test("invalid JSON returns null", () => {
|
|
const data = Buffer.from("{invalid}");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toBeNull();
|
|
});
|
|
|
|
test("no JSON returns null", () => {
|
|
const data = Buffer.from("plain text");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toBeNull();
|
|
});
|
|
|
|
test("incomplete JSON returns null", () => {
|
|
const data = Buffer.from('{"type":13');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("parse() - device status detection", () => {
|
|
test("type 13 (number) sets isStatus true", () => {
|
|
const data = Buffer.from('{"type":13}');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.isStatus).toBe(true);
|
|
});
|
|
|
|
test('type "13" (string) sets isStatus true', () => {
|
|
const data = Buffer.from('{"type":"13"}');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.isStatus).toBe(true);
|
|
});
|
|
|
|
test("type 6 sets isStatus false", () => {
|
|
const data = Buffer.from('{"type":6}');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.isStatus).toBe(false);
|
|
});
|
|
|
|
test("no type field sets isStatus false", () => {
|
|
const data = Buffer.from('{"value":42}');
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.isStatus).toBe(false);
|
|
});
|
|
|
|
test("null jsonData sets isStatus false", () => {
|
|
const data = Buffer.from("not json");
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.isStatus).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("parse() - device status parsing", () => {
|
|
test("full status object", () => {
|
|
const statusJson = {
|
|
type: 13,
|
|
allspace: 16384,
|
|
freespace: 13892,
|
|
devname: "BeamBox",
|
|
size: "64x32",
|
|
brand: 1,
|
|
};
|
|
const data = Buffer.from(JSON.stringify(statusJson));
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.isStatus).toBe(true);
|
|
expect(result.deviceStatus).toBeDefined();
|
|
expect(result.deviceStatus?.type).toBe(PacketType.DEVICE_STATUS);
|
|
expect(result.deviceStatus?.allspace).toBe(16384);
|
|
expect(result.deviceStatus?.freespace).toBe(13892);
|
|
expect(result.deviceStatus?.devname).toBe("BeamBox");
|
|
expect(result.deviceStatus?.size).toBe("64x32");
|
|
expect(result.deviceStatus?.brand).toBe(1);
|
|
});
|
|
|
|
test("missing fields default to 0 or empty string", () => {
|
|
const data = Buffer.from('{"type":13}');
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.deviceStatus?.type).toBe(PacketType.DEVICE_STATUS);
|
|
expect(result.deviceStatus?.allspace).toBe(0);
|
|
expect(result.deviceStatus?.freespace).toBe(0);
|
|
expect(result.deviceStatus?.devname).toBe("");
|
|
expect(result.deviceStatus?.size).toBe("");
|
|
expect(result.deviceStatus?.brand).toBe(0);
|
|
});
|
|
|
|
test("string numbers converted to Number", () => {
|
|
const data = Buffer.from('{"type":13,"allspace":"16384","freespace":"13892","brand":"1"}');
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.deviceStatus?.allspace).toBe(16384);
|
|
expect(result.deviceStatus?.freespace).toBe(13892);
|
|
expect(result.deviceStatus?.brand).toBe(1);
|
|
expect(typeof result.deviceStatus?.allspace).toBe("number");
|
|
});
|
|
|
|
test("invalid number values default to 0", () => {
|
|
const data = Buffer.from('{"type":13,"allspace":"invalid","brand":"bad"}');
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.deviceStatus?.allspace).toBe(0);
|
|
expect(result.deviceStatus?.brand).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("helper methods", () => {
|
|
test("isSuccess() checks ResponseStatus.SUCCESS", () => {
|
|
const data = Buffer.from("GetPacketSuccess");
|
|
const result = ResponseParser.parse(data);
|
|
expect(ResponseParser.isSuccess(result)).toBe(true);
|
|
expect(ResponseParser.isFail(result)).toBe(false);
|
|
expect(ResponseParser.isError(result)).toBe(false);
|
|
});
|
|
|
|
test("isFail() checks ResponseStatus.FAIL", () => {
|
|
const data = Buffer.from("GetPacketFail");
|
|
const result = ResponseParser.parse(data);
|
|
expect(ResponseParser.isSuccess(result)).toBe(false);
|
|
expect(ResponseParser.isFail(result)).toBe(true);
|
|
expect(ResponseParser.isError(result)).toBe(false);
|
|
});
|
|
|
|
test("isError() checks ResponseStatus.ERROR", () => {
|
|
const data = Buffer.from("1111111111");
|
|
const result = ResponseParser.parse(data);
|
|
expect(ResponseParser.isSuccess(result)).toBe(false);
|
|
expect(ResponseParser.isFail(result)).toBe(false);
|
|
expect(ResponseParser.isError(result)).toBe(true);
|
|
});
|
|
|
|
test("all return false for null status", () => {
|
|
const data = Buffer.from("random");
|
|
const result = ResponseParser.parse(data);
|
|
expect(ResponseParser.isSuccess(result)).toBe(false);
|
|
expect(ResponseParser.isFail(result)).toBe(false);
|
|
expect(ResponseParser.isError(result)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("real packet tests", () => {
|
|
test("parse GetPacketSuccess response", () => {
|
|
const data = Buffer.from(knownPackets.getPacketSuccess.text);
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.rawText).toBe("GetPacketSuccess");
|
|
expect(result.status).toBe(ResponseStatus.SUCCESS);
|
|
expect(ResponseParser.isSuccess(result)).toBe(true);
|
|
});
|
|
|
|
test("parse GetPacketFail response", () => {
|
|
const data = Buffer.from(knownPackets.getPacketFail.text);
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.rawText).toBe("GetPacketFail");
|
|
expect(result.status).toBe(ResponseStatus.FAIL);
|
|
expect(ResponseParser.isFail(result)).toBe(true);
|
|
});
|
|
|
|
test("parse error response", () => {
|
|
const data = Buffer.from(knownPackets.errorResponse.text);
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.rawText).toBe("1111111111");
|
|
expect(result.status).toBe(ResponseStatus.ERROR);
|
|
expect(ResponseParser.isError(result)).toBe(true);
|
|
});
|
|
|
|
test("parse device status response", () => {
|
|
const data = hexToBuffer(knownPackets.deviceStatusResponse.hex);
|
|
const result = ResponseParser.parse(data);
|
|
|
|
expect(result.isStatus).toBe(true);
|
|
expect(result.jsonData).toEqual(knownPackets.deviceStatusResponse.json);
|
|
expect(result.deviceStatus?.type).toBe(13);
|
|
expect(result.deviceStatus?.allspace).toBe(16384);
|
|
expect(result.deviceStatus?.freespace).toBe(13892);
|
|
expect(result.deviceStatus?.devname).toBe("BeamBox");
|
|
expect(result.deviceStatus?.size).toBe("64x32");
|
|
expect(result.deviceStatus?.brand).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("edge cases", () => {
|
|
test("very long text", () => {
|
|
const longText = "a".repeat(10000);
|
|
const data = Buffer.from(longText);
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.rawText).toBe(longText);
|
|
});
|
|
|
|
test("binary data (non-UTF8)", () => {
|
|
const data = Buffer.from([0xff, 0xfe, 0xfd, 0xfc]);
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.status).toBeNull();
|
|
expect(result.jsonData).toBeNull();
|
|
});
|
|
|
|
test("nested JSON", () => {
|
|
const nested = { type: 13, data: { nested: { value: 42 } } };
|
|
const data = Buffer.from(JSON.stringify(nested));
|
|
const result = ResponseParser.parse(data);
|
|
expect(result.jsonData).toEqual(nested);
|
|
expect(result.isStatus).toBe(true);
|
|
});
|
|
|
|
test("JSON array is parsed", () => {
|
|
const data = Buffer.from('[{"type":13}]');
|
|
const result = ResponseParser.parse(data);
|
|
// JSON.parse will parse arrays too
|
|
expect(Array.isArray(result.jsonData)).toBe(true);
|
|
});
|
|
|
|
test("multiple JSON objects (first is parsed if valid JSON)", () => {
|
|
const data = Buffer.from('{"type":13}{"type":6}');
|
|
const result = ResponseParser.parse(data);
|
|
// This is invalid JSON, so extraction will try to find {...}
|
|
// It might find the first object
|
|
expect(result.jsonData).toBeDefined();
|
|
});
|
|
});
|
|
});
|