@firfi/quint-connect
Version:
Model-based testing framework connecting Quint specifications to TypeScript implementations
50 lines • 3.08 kB
JavaScript
import { Effect, Predicate, Schema } from "effect";
import { buildEffectPicksDecoder } from "../itf/picks.js";
import { MbtMeta as MbtMetaSchema } from "../itf/schema.js";
import { actionContext, traceReplayError } from "./replay-errors.js";
import { resolveNestedValue, stripMetadata } from "./trace-state.js";
const extractMbtMeta = (state, context) => Effect.mapError(Schema.decodeUnknown(MbtMetaSchema)(state), (cause) => traceReplayError(actionContext(context, "unknown"), `Failed to extract MBT metadata: ${cause}`));
const isItfOption = (value) => Predicate.isRecord(value) && (value["tag"] === "Some" || value["tag"] === "None");
const normalizeNondetPathPick = (value) => isItfOption(value) ? value : { tag: "Some", value };
const normalizeNondetPathPicks = (value) => Predicate.isRecord(value)
? new Map(Object.entries(value).map(([key, pick]) => [key, normalizeNondetPathPick(pick)]))
: new Map();
const extractFromNondetPath = (state, nondetPath, context) => {
const raw = resolveNestedValue(state, nondetPath);
if (!Predicate.isRecord(raw) || typeof raw["tag"] !== "string") {
return Effect.fail(traceReplayError(actionContext(context, "unknown"), `Expected sum type {tag, value} at path ${nondetPath.join(".")}, got: ${JSON.stringify(raw)}`));
}
const action = raw["tag"];
const value = raw["value"];
return Effect.succeed({ action, nondetPicks: normalizeNondetPathPicks(value) });
};
const decodeReplayAction = (state, nondetPath, context) => nondetPath.length > 0
? extractFromNondetPath(state, nondetPath, context)
: Effect.map(extractMbtMeta(state, context), (meta) => ({
action: meta["mbt::actionTaken"],
nondetPicks: new Map(Object.entries(meta["mbt::nondetPicks"]))
}));
const projectSpecState = (state, statePath, action, context) => {
if (statePath.length === 0) {
return Effect.succeed(stripMetadata(state));
}
const projected = resolveNestedValue(state, statePath);
return projected === undefined
? Effect.fail(traceReplayError(actionContext(context, action), `Expected state at path ${statePath.join(".")}, got: undefined`))
: Effect.succeed(projected);
};
/** Decode action and picks without projecting unused specification state. */
export const extractReplayAction = decodeReplayAction;
/** Decode all data needed to dispatch and check one trace state. */
export const decodeReplayStep = (state, config, context) => Effect.gen(function* () {
const replayAction = yield* decodeReplayAction(state, config.nondetPath ?? [], context);
const specState = replayAction.action !== ""
? yield* projectSpecState(state, config.statePath ?? [], replayAction.action, context)
: undefined;
return { ...replayAction, specState };
});
export const buildPicksDecoder = (buildEffectPicksDecoder);
export const buildPicksDecoders = (actions) => new Map(Object.entries(actions)
.filter((entry) => entry[1] !== undefined)
.map(([name, def]) => [name, buildPicksDecoder(def.picks)]));
//# sourceMappingURL=replay-actions.js.map