feat(S8): ingest through the bank — capture/import/drop into bank + assign to active instance
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
// Standalone tests for reasampler::AssignmentRequest — no REAPER, no framework.
|
||||
// The S8 ingest assignment-request seam: the (bankId, sampleId, generation) value the
|
||||
// extension writes to ext-state after an ingest-with-assign, decoded by the instrument
|
||||
// in a later dispatch. Only the wire format lives in this module; test it hard because
|
||||
// the reader (a different artifact) must decode exactly what this writer produces.
|
||||
//
|
||||
// Covers: encode/decode round-trip, ids carrying arbitrary bytes (GUIDs, separators),
|
||||
// the generation field including zero and negative-guard, and malformed/truncated/
|
||||
// trailing-garbage input -> nullopt (the reader's "no pending request" fallback hinges
|
||||
// on it).
|
||||
|
||||
#include "../src/assignment_request.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
using namespace reasampler;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||
|
||||
// --- round-trip --------------------------------------------------------------
|
||||
|
||||
static void testRoundTrip() {
|
||||
AssignmentRequest req;
|
||||
req.bankId = "{12345678-1234-1234-1234-1234567890AB}";
|
||||
req.sampleId = "cap-1700000000-kick.wav";
|
||||
req.generation = 1700000123;
|
||||
|
||||
const std::string wire = encodeAssignmentRequest(req);
|
||||
auto back = decodeAssignmentRequest(wire);
|
||||
CHECK(back.has_value());
|
||||
CHECK(*back == req);
|
||||
// Re-encoding the decoded value is byte-stable (deterministic encoder).
|
||||
CHECK(encodeAssignmentRequest(*back) == wire);
|
||||
}
|
||||
|
||||
// The pool bank id and an empty-ish generation must round-trip too (generation 0 is the
|
||||
// documented pre-S9 default; an assign still carries a real stamp, but 0 must be legal).
|
||||
static void testRoundTripPoolAndZeroGeneration() {
|
||||
AssignmentRequest req;
|
||||
req.bankId = "pool";
|
||||
req.sampleId = "s1";
|
||||
req.generation = 0;
|
||||
|
||||
auto back = decodeAssignmentRequest(encodeAssignmentRequest(req));
|
||||
CHECK(back.has_value());
|
||||
CHECK(back->bankId == "pool");
|
||||
CHECK(back->sampleId == "s1");
|
||||
CHECK(back->generation == 0);
|
||||
}
|
||||
|
||||
// Ids carrying the wire's own metacharacters (':' the length delimiter, digits that
|
||||
// could be misread as a length, the magic-tag bytes) must survive whole — the whole
|
||||
// reason for length-prefixing over a delimiter-split format.
|
||||
static void testRoundTripAdversarialIds() {
|
||||
AssignmentRequest req;
|
||||
req.bankId = "12:34:has-colons"; // ':' is the length delimiter
|
||||
req.sampleId = "rsassign1-lookalike-99"; // embeds the magic tag
|
||||
req.generation = -42; // negative is representable
|
||||
|
||||
auto back = decodeAssignmentRequest(encodeAssignmentRequest(req));
|
||||
CHECK(back.has_value());
|
||||
CHECK(*back == req);
|
||||
CHECK(back->bankId == "12:34:has-colons");
|
||||
CHECK(back->sampleId == "rsassign1-lookalike-99");
|
||||
CHECK(back->generation == -42);
|
||||
}
|
||||
|
||||
// Empty ids are structurally valid on the wire (length 0) and must round-trip — the
|
||||
// decoder must not conflate an empty field with a parse failure.
|
||||
static void testRoundTripEmptyFields() {
|
||||
AssignmentRequest req;
|
||||
req.bankId = "";
|
||||
req.sampleId = "";
|
||||
req.generation = 7;
|
||||
|
||||
auto back = decodeAssignmentRequest(encodeAssignmentRequest(req));
|
||||
CHECK(back.has_value());
|
||||
CHECK(*back == req);
|
||||
}
|
||||
|
||||
// A large generation (past 32-bit) must not truncate — the field is int64.
|
||||
static void testLargeGeneration() {
|
||||
AssignmentRequest req;
|
||||
req.bankId = "b";
|
||||
req.sampleId = "s";
|
||||
req.generation = 9007199254740993LL; // > 2^53, > INT32_MAX
|
||||
|
||||
auto back = decodeAssignmentRequest(encodeAssignmentRequest(req));
|
||||
CHECK(back.has_value());
|
||||
CHECK(back->generation == 9007199254740993LL);
|
||||
}
|
||||
|
||||
// --- malformed / tolerant parse ----------------------------------------------
|
||||
|
||||
static void testMalformedParse() {
|
||||
// Absence / total garbage — the reader maps these to "no pending request".
|
||||
CHECK(!decodeAssignmentRequest("").has_value());
|
||||
CHECK(!decodeAssignmentRequest("not a request").has_value());
|
||||
// Wrong magic tag.
|
||||
CHECK(!decodeAssignmentRequest("rsprov1" "1:b1:s1:7").has_value());
|
||||
// Magic only, no fields.
|
||||
CHECK(!decodeAssignmentRequest("rsassign1").has_value());
|
||||
// Truncated mid-record (missing the generation field).
|
||||
CHECK(!decodeAssignmentRequest("rsassign1" "4:pool2:s1").has_value());
|
||||
// A length that runs past the end.
|
||||
CHECK(!decodeAssignmentRequest("rsassign1" "99:short").has_value());
|
||||
// Non-numeric length token.
|
||||
CHECK(!decodeAssignmentRequest("rsassign1" "x:pool2:s11:7").has_value());
|
||||
// A non-numeric generation field.
|
||||
CHECK(!decodeAssignmentRequest("rsassign1" "4:pool2:s13:abc").has_value());
|
||||
// A bare "-" generation.
|
||||
CHECK(!decodeAssignmentRequest("rsassign1" "4:pool2:s11:-").has_value());
|
||||
}
|
||||
|
||||
// Trailing garbage after a well-formed record must be rejected — a partial/padded blob
|
||||
// is not a valid request, and the reader must never accept the prefix and ignore the rest.
|
||||
static void testTrailingGarbageRejected() {
|
||||
AssignmentRequest req;
|
||||
req.bankId = "pool";
|
||||
req.sampleId = "s1";
|
||||
req.generation = 7;
|
||||
const std::string wire = encodeAssignmentRequest(req);
|
||||
|
||||
// The clean value parses.
|
||||
CHECK(decodeAssignmentRequest(wire).has_value());
|
||||
// The same value with any trailing byte does not.
|
||||
CHECK(!decodeAssignmentRequest(wire + "X").has_value());
|
||||
CHECK(!decodeAssignmentRequest(wire + "0:").has_value());
|
||||
}
|
||||
|
||||
int main() {
|
||||
testRoundTrip();
|
||||
testRoundTripPoolAndZeroGeneration();
|
||||
testRoundTripAdversarialIds();
|
||||
testRoundTripEmptyFields();
|
||||
testLargeGeneration();
|
||||
testMalformedParse();
|
||||
testTrailingGarbageRejected();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user