feat: add local access control and managed point clouds
This commit is contained in:
125
server/pointcloudMetadata.test.ts
Normal file
125
server/pointcloudMetadata.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
inspectPointcloudMetadata,
|
||||
MAX_PLY_HEADER_BYTES,
|
||||
parsePlyHeader,
|
||||
PointcloudMetadataParseError,
|
||||
} from "./pointcloudMetadata";
|
||||
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
while (temporaryDirectories.length > 0) {
|
||||
rmSync(temporaryDirectories.pop()!, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function header(lines: string[], newline = "\n") {
|
||||
return Buffer.from([...lines, "end_header", ""].join(newline), "ascii");
|
||||
}
|
||||
|
||||
describe("parsePlyHeader", () => {
|
||||
it("解析 ASCII PLY,并以 element vertex 作为精确点数", () => {
|
||||
const result = parsePlyHeader(header([
|
||||
"ply",
|
||||
"format ascii 1.0",
|
||||
"element vertex 1000000000",
|
||||
"property float x",
|
||||
"property float y",
|
||||
"property double z",
|
||||
"element face 999",
|
||||
"property list uchar int vertex_indices",
|
||||
]));
|
||||
|
||||
expect(result).toEqual({
|
||||
pointCount: 1_000_000_000,
|
||||
pointDataFormat: "ascii",
|
||||
bytesPerPoint: 16,
|
||||
pointAttributes: [
|
||||
{ name: "x", type: "float32", bytes: 4 },
|
||||
{ name: "y", type: "float32", bytes: 4 },
|
||||
{ name: "z", type: "float64", bytes: 8 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["binary_little_endian", "binary_little_endian"],
|
||||
["binary_big_endian", "binary_big_endian"],
|
||||
] as const)("解析 %s PLY", (format, expected) => {
|
||||
const result = parsePlyHeader(header([
|
||||
"ply",
|
||||
`format ${format} 1.0`,
|
||||
"element vertex 42",
|
||||
"property int16 x",
|
||||
"property ushort intensity",
|
||||
"property uchar red",
|
||||
]));
|
||||
expect(result.pointDataFormat).toBe(expected);
|
||||
expect(result.bytesPerPoint).toBe(5);
|
||||
});
|
||||
|
||||
it("支持 CRLF 文件头和类型别名", () => {
|
||||
const result = parsePlyHeader(header([
|
||||
"ply",
|
||||
"format ascii 1.0",
|
||||
"comment generated",
|
||||
"element vertex 3",
|
||||
"property char classification",
|
||||
"property uint confidence",
|
||||
"property float32 temperature",
|
||||
], "\r\n"));
|
||||
expect(result.pointAttributes).toEqual([
|
||||
{ name: "classification", type: "int8", bytes: 1 },
|
||||
{ name: "confidence", type: "uint32", bytes: 4 },
|
||||
{ name: "temperature", type: "float32", bytes: 4 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("拒绝无效文件头", () => {
|
||||
expect(() => parsePlyHeader(Buffer.from("not-ply\n"))).toThrow(PointcloudMetadataParseError);
|
||||
});
|
||||
|
||||
it("拒绝未知的顶点属性类型", () => {
|
||||
expect(() => parsePlyHeader(header([
|
||||
"ply",
|
||||
"format ascii 1.0",
|
||||
"element vertex 1",
|
||||
"property half x",
|
||||
]))).toThrow("未知的顶点属性类型");
|
||||
});
|
||||
|
||||
it("拒绝列表型顶点属性,但忽略其他 element 的列表属性", () => {
|
||||
expect(() => parsePlyHeader(header([
|
||||
"ply",
|
||||
"format ascii 1.0",
|
||||
"element vertex 1",
|
||||
"property list uchar float samples",
|
||||
]))).toThrow("列表型顶点属性");
|
||||
});
|
||||
|
||||
it("非 PLY 格式直接标记为 unsupported,不读取文件", () => {
|
||||
expect(inspectPointcloudMetadata("/path/does/not/exist.txt", "cloud.txt")).toEqual({
|
||||
metadataStatus: "unsupported",
|
||||
pointCount: null,
|
||||
pointAttributes: null,
|
||||
pointDataFormat: null,
|
||||
bytesPerPoint: null,
|
||||
metadataError: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("只读取 16KB 文件头,超长文件头标记为解析失败", () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), "pointcloud-metadata-"));
|
||||
temporaryDirectories.push(directory);
|
||||
const filePath = join(directory, "oversized.ply");
|
||||
writeFileSync(filePath, `ply\nformat ascii 1.0\ncomment ${"x".repeat(MAX_PLY_HEADER_BYTES)}\nend_header\n`);
|
||||
|
||||
const result = inspectPointcloudMetadata(filePath, "oversized.ply");
|
||||
expect(result.metadataStatus).toBe("error");
|
||||
expect(result.metadataError).toContain("16KB");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user