Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
172 lines
5.5 KiB
JavaScript
172 lines
5.5 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import {
|
|
readParameterFacet,
|
|
createParameterViews,
|
|
} from "./native-parameters.mjs";
|
|
import { parseNativeIR } from "./native-ir.mjs";
|
|
import { methods, reference } from "./api.mjs";
|
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
|
|
const parameter = (
|
|
index,
|
|
storage = { space: "register", offset: "0x38", size_bytes: 8 },
|
|
) => ({
|
|
id: index === null ? "return" : `parameter:${index}`,
|
|
native_index: index,
|
|
name: "display",
|
|
type_name: "unknown8",
|
|
size_bytes: 8,
|
|
is_void: false,
|
|
storage,
|
|
type_locked: false,
|
|
name_locked: false,
|
|
size_locked: false,
|
|
this_pointer: false,
|
|
indirect_storage: false,
|
|
hidden_return: false,
|
|
});
|
|
const fixture = () => ({
|
|
schema_version: 1,
|
|
model: "__stdcall",
|
|
model_unknown: false,
|
|
inputs_locked: false,
|
|
output_locked: false,
|
|
variadic: false,
|
|
input_errors: false,
|
|
output_errors: true,
|
|
items: [
|
|
parameter(0),
|
|
parameter(1, { space: "stack", offset: "0x8", size_bytes: 8 }),
|
|
],
|
|
output: parameter(null, { space: "register", offset: "0x0", size_bytes: 8 }),
|
|
});
|
|
const read = (raw) => readParameterFacet({ atomic_parameters: raw });
|
|
const model = (fn) =>
|
|
parseNativeIR(
|
|
JSON.stringify({
|
|
binary: "/input",
|
|
count: 1,
|
|
language: "c",
|
|
error: null,
|
|
functions: [
|
|
{ address_hex: "0x1000", error: null, code: "readable", ...fn },
|
|
],
|
|
}),
|
|
"0x1000",
|
|
);
|
|
|
|
test("formal selection preserves native indices, caller order, unknowns and independently selected output", () => {
|
|
const parsed = read(fixture());
|
|
assert.equal(parsed.ok, true);
|
|
const view = createParameterViews(parsed.value);
|
|
const first = view.parameters({
|
|
indices: [1, 0, 1, 8],
|
|
fields: ["storage", "size_bytes"],
|
|
limit: 1,
|
|
});
|
|
assert.deepEqual(
|
|
first.items.map((r) => r.id),
|
|
["parameter:1"],
|
|
);
|
|
assert.equal(first.total, 2);
|
|
assert.equal(first.next_offset, 1);
|
|
assert.deepEqual(first.missing_indices, [8]);
|
|
assert.equal(first.output.id, "return");
|
|
assert.equal(first.prototype.output_errors, true);
|
|
assert.equal(first.coverage.source_signature, "unknown");
|
|
const next = view.parameters({ indices: [1, 0], fields: [], offset: 1 });
|
|
assert.deepEqual(next.items, [{ id: "parameter:0", native_index: 0 }]);
|
|
assert.deepEqual(next.output, { id: "return", native_index: null });
|
|
assert.deepEqual(view.parameters({ indices: [] }).items, []);
|
|
assert.equal(Object.hasOwn(view.parameters({}).items[0], "type_name"), false);
|
|
});
|
|
test("cached metadata/storage cannot be changed by SDK callers", () => {
|
|
const view = createParameterViews(read(fixture()).value);
|
|
const result = view.parameters({});
|
|
assert.throws(() => {
|
|
result.prototype.model = "changed";
|
|
}, TypeError);
|
|
assert.throws(() => {
|
|
result.items[0].storage.offset = "0x0";
|
|
}, TypeError);
|
|
result.items.length = 0;
|
|
assert.equal(view.parameters({}).total, 2);
|
|
assert.equal(view.parameters({}).prototype.model, "__stdcall");
|
|
});
|
|
test("unknown type/storage is distinct from native void, malformed identities cannot publish partial formals", () => {
|
|
const unknown = fixture();
|
|
Object.assign(unknown.output, {
|
|
type_name: null,
|
|
is_void: null,
|
|
storage: null,
|
|
});
|
|
assert.equal(read(unknown).ok, true);
|
|
const empty = fixture();
|
|
empty.items = [];
|
|
Object.assign(empty.output, {
|
|
type_name: "void",
|
|
is_void: true,
|
|
size_bytes: 0,
|
|
storage: null,
|
|
});
|
|
assert.equal(read(empty).ok, true);
|
|
for (const change of [
|
|
(x) => (x.items[0].id = "parameter:9"),
|
|
(x) => (x.items[1].native_index = 0),
|
|
(x) => (x.output.native_index = 0),
|
|
(x) => (x.items[0].size_bytes = 4),
|
|
(x) => (x.output.is_void = true),
|
|
(x) => (x.output.type_name = null),
|
|
(x) => (x.model_unknown = null),
|
|
]) {
|
|
const x = fixture();
|
|
change(x);
|
|
assert.equal(read(x).ok, false);
|
|
}
|
|
});
|
|
test("prototype facet works without graph/calls; unavailable/invalid prototype retains code and reason", () => {
|
|
const available = model({
|
|
atomic_parameters: fixture(),
|
|
atomic_facts_error: { malformed: "not a failure string" },
|
|
atomic_calls_error: "capacity",
|
|
});
|
|
assert.equal(available.facets.parameters.availability, "available");
|
|
assert.equal(available.parameters({}).total, 2);
|
|
assert.equal(available.facets.recovered_ir.availability, "unavailable");
|
|
assert.equal(available.code, "readable");
|
|
for (const fn of [
|
|
{},
|
|
{
|
|
atomic_parameters_error: "native function prototype store is unavailable",
|
|
},
|
|
{ atomic_parameters: { schema_version: 99 } },
|
|
]) {
|
|
const unavailable = model(fn);
|
|
assert.equal(unavailable.code, "readable");
|
|
assert.equal(unavailable.facets.parameters.availability, "unavailable");
|
|
assert.throws(
|
|
() => unavailable.parameters({}),
|
|
(e) => e.details.facet === "parameters",
|
|
);
|
|
}
|
|
});
|
|
test("parameter input schema matches advertised JSON Schema and rejects guessed keys/indices", () => {
|
|
const validate = new Ajv2020({ strict: false }).compile(
|
|
reference("parameters").methods[0].input_schema,
|
|
);
|
|
for (const [request, valid] of [
|
|
[{}, true],
|
|
[{ indices: [], fields: [] }, true],
|
|
[{ indices: [1, 0, 1], fields: ["storage", "type_name"] }, true],
|
|
[{ indices: [-1] }, false],
|
|
[{ indices: ["0"] }, false],
|
|
[{ fields: ["value_id"] }, false],
|
|
[{ signature: true }, false],
|
|
]) {
|
|
const input = { snapshot: "snap_test", ...request };
|
|
assert.equal(methods.parameters.input.safeParse(input).success, valid);
|
|
assert.equal(validate(input), valid);
|
|
}
|
|
});
|