dal-engine-core-js-lib-dev 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +53 -0
- package/dist/index.cjs +486 -0
- package/dist/index.esm.js +484 -0
- package/eslint.config.js +83 -0
- package/package.json +35 -0
- package/rollup.config.js +16 -0
- package/src/Base.js +18 -0
- package/src/BehavioralControlGraph/BehavioralControlGraph.js +111 -0
- package/src/BehavioralControlGraph/GraphNode.js +62 -0
- package/src/DALEngine.js +73 -0
- package/src/Errors/DALEngineError.js +7 -0
- package/src/Errors/InvalidTransitionError.js +13 -0
- package/src/Errors/UnknownBehaviorError.js +12 -0
- package/src/Members/Behavior.js +70 -0
- package/src/Members/Invariant.js +69 -0
- package/src/Members/Participant.js +76 -0
- package/src/TYPES.js +11 -0
- package/tests/DALEngine.test.js +130 -0
- package/tests/Invariant.test.js +93 -0
- package/vitest.config.js +8 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import Base from "../Base";
|
|
2
|
+
import InvalidTransitionError from "../Errors/InvalidTransitionError";
|
|
3
|
+
import UnknownBehaviorError from "../Errors/UnknownBehaviorError";
|
|
4
|
+
import ENGINE_TYPES from "../TYPES";
|
|
5
|
+
import GraphNode from "./GraphNode";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Class representing the behavioral control graph.
|
|
9
|
+
*/
|
|
10
|
+
class BehavioralControlGraph extends Base {
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the behavioral control graph.
|
|
13
|
+
* @param {String} name
|
|
14
|
+
* @param args
|
|
15
|
+
*/
|
|
16
|
+
constructor (args) {
|
|
17
|
+
super();
|
|
18
|
+
this.type = ENGINE_TYPES.BEHAVIORAL_CONTROL_GRAPH;
|
|
19
|
+
this.nodes = [];
|
|
20
|
+
if (typeof args === "object" && args !== null) {
|
|
21
|
+
if (Object.hasOwn(args, "uid")) {
|
|
22
|
+
this.loadGraphFromJSON(args);
|
|
23
|
+
} else {
|
|
24
|
+
this.name = args.name;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Loads the graph from a JSON object..
|
|
31
|
+
* @param {Object} graphJson
|
|
32
|
+
*/
|
|
33
|
+
loadGraphFromJSON (graphJson) {
|
|
34
|
+
for (const [key, value] of Object.entries(graphJson)) {
|
|
35
|
+
if (key === "nodes") {
|
|
36
|
+
value.forEach(node => this.nodes.push(new GraphNode(node)));
|
|
37
|
+
} else {
|
|
38
|
+
this[key] = graphJson[key];
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Adds a node to the graph.
|
|
45
|
+
* @param {Behavior} behavior
|
|
46
|
+
* @param {Array} goToBehaviors
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
addNode (behavior, goToBehaviors) {
|
|
50
|
+
const node = new GraphNode({
|
|
51
|
+
behavior: behavior,
|
|
52
|
+
goToBehaviors: goToBehaviors,
|
|
53
|
+
});
|
|
54
|
+
this.nodes.push(node);
|
|
55
|
+
return node;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Finds the given node given the behavior name.
|
|
61
|
+
* @param {String} behaviorName
|
|
62
|
+
* @throws {UnknownBehaviorError} Raised when the provided behavior
|
|
63
|
+
* does not exist in the graph.
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
findNode (behaviorName) {
|
|
67
|
+
for (let i = 0; i < this.nodes.length; i++) {
|
|
68
|
+
const behavior = this.nodes[i].behavior;
|
|
69
|
+
if (behavior.name === behaviorName) {
|
|
70
|
+
return this.nodes[i];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw new UnknownBehaviorError(behaviorName);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sets the active node given the behavior name.
|
|
78
|
+
* The execution provides the next observed
|
|
79
|
+
* behavior and the active node indicates if
|
|
80
|
+
* if it is a valid transition.
|
|
81
|
+
*
|
|
82
|
+
*
|
|
83
|
+
* @param {String} behaviorName
|
|
84
|
+
*/
|
|
85
|
+
setCurrentBehavior (behaviorName) {
|
|
86
|
+
const node = this.findNode(behaviorName);
|
|
87
|
+
/**
|
|
88
|
+
* TODO: Ensure it is atomic because the execution
|
|
89
|
+
* will only set a behavior when its the first one.
|
|
90
|
+
* It will walk using goToBehavior after that.
|
|
91
|
+
*/
|
|
92
|
+
this.currentNode = node;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if the observed behavior is a valid transition
|
|
97
|
+
* given the current node.
|
|
98
|
+
* @param {String} nextBehaviorName
|
|
99
|
+
* @throws {InvalidTransitionError} Raised when the provided
|
|
100
|
+
* behavior is not a valid transition.
|
|
101
|
+
*/
|
|
102
|
+
goToBehavior (nextBehaviorName) {
|
|
103
|
+
if (this.currentNode.isValidGoToBehavior(nextBehaviorName)) {
|
|
104
|
+
this.currentNode = this.findNode(nextBehaviorName);
|
|
105
|
+
} else {
|
|
106
|
+
throw new InvalidTransitionError(this.currentNode.behavior.name, nextBehaviorName);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default BehavioralControlGraph;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Base from "../Base";
|
|
2
|
+
import Behavior from "../Members/Behavior";
|
|
3
|
+
import ENGINE_TYPES from "../TYPES";
|
|
4
|
+
/**
|
|
5
|
+
* Class representing a behavioral control graph node.
|
|
6
|
+
*/
|
|
7
|
+
class GraphNode extends Base {
|
|
8
|
+
/**
|
|
9
|
+
* Initialize the node.
|
|
10
|
+
* @param {String} name
|
|
11
|
+
* @param args
|
|
12
|
+
*/
|
|
13
|
+
constructor (args) {
|
|
14
|
+
super();
|
|
15
|
+
this.type = ENGINE_TYPES.GRAPH_NODE;
|
|
16
|
+
this.goToBehaviors = [];
|
|
17
|
+
if (typeof args === "object" && args !== null) {
|
|
18
|
+
if (Object.hasOwn(args, "uid")) {
|
|
19
|
+
this.loadNodeFromJSON(args);
|
|
20
|
+
} else {
|
|
21
|
+
this.behavior = args.behavior;
|
|
22
|
+
this.goToBehaviors = args.goToBehaviors;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Loads the nodes from a JSON object.
|
|
29
|
+
* @param {Object} nodesJSON
|
|
30
|
+
*/
|
|
31
|
+
loadNodeFromJSON (nodesJSON) {
|
|
32
|
+
for (const [key, value] of Object.entries(nodesJSON)) {
|
|
33
|
+
if (key === "behavior") {
|
|
34
|
+
this.behavior = new Behavior(value);
|
|
35
|
+
} else if (key === "goToBehaviors") {
|
|
36
|
+
value.forEach(node => this.goToBehaviors.push(new Behavior(node)));
|
|
37
|
+
} else {
|
|
38
|
+
this[key] = nodesJSON[key];
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Checks if the provided behavior name is a valid
|
|
45
|
+
* behavior that the control flow selects as a
|
|
46
|
+
* result of the this nodes state transformation. i.e.
|
|
47
|
+
* is this behavior in the goToBehavior list.
|
|
48
|
+
* @param {String} behaviorName
|
|
49
|
+
* @returns {Boolean}
|
|
50
|
+
*/
|
|
51
|
+
isValidGoToBehavior (behaviorName) {
|
|
52
|
+
for (let i = 0; i < this.goToBehaviors.length; i++) {
|
|
53
|
+
const behavior = this.goToBehaviors[i];
|
|
54
|
+
if (behavior.name === behaviorName) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default GraphNode;
|
package/src/DALEngine.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import BehavioralControlGraph from "./BehavioralControlGraph/BehavioralControlGraph";
|
|
2
|
+
import Behavior from "./Members/Behavior";
|
|
3
|
+
import Invariant from "./Members/Invariant";
|
|
4
|
+
import Participant from "./Members/Participant";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* An object representing an engine written in Design
|
|
8
|
+
* abstraction language. It exposes functions
|
|
9
|
+
* configure the engine through the DAL specification.
|
|
10
|
+
*
|
|
11
|
+
* The execution of an program instrumented with the
|
|
12
|
+
* same design be used to step through the design
|
|
13
|
+
* while the engine automatically debugs the execution.
|
|
14
|
+
*/
|
|
15
|
+
export class DALEngine {
|
|
16
|
+
constructor (args) {
|
|
17
|
+
this.graph = new BehavioralControlGraph();
|
|
18
|
+
for (const [key, value] of Object.entries(args)) {
|
|
19
|
+
this[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Exports the behavioral control graph to JSON text.
|
|
25
|
+
* @returns {String}
|
|
26
|
+
*/
|
|
27
|
+
serialize () {
|
|
28
|
+
return JSON.stringify(this.graph);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Import the behavioral control graph from JSON text.
|
|
33
|
+
* @param {String} jsonText
|
|
34
|
+
*/
|
|
35
|
+
deserialize (jsonText) {
|
|
36
|
+
this.graph = new BehavioralControlGraph();
|
|
37
|
+
this.graph.loadGraphFromJSON(JSON.parse(jsonText));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a participant.
|
|
42
|
+
* @param {Object} args
|
|
43
|
+
* @returns {Participant}
|
|
44
|
+
*/
|
|
45
|
+
createParticipant (args) {
|
|
46
|
+
// TODO: Validate that the args have the necessary keys
|
|
47
|
+
// and raise custom error if they are missing.
|
|
48
|
+
// Perhaps it is better to do that in the class itself.
|
|
49
|
+
return new Participant(args);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates a behavior.
|
|
54
|
+
* @param {Object} args
|
|
55
|
+
* @returns {Behavior}
|
|
56
|
+
*/
|
|
57
|
+
createBehavior (args) {
|
|
58
|
+
// TODO: Validate that the args have the necessary keys
|
|
59
|
+
// and raise custom error if they are missing.
|
|
60
|
+
return new Behavior(args);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates an invariant.
|
|
65
|
+
* @param {Object} args
|
|
66
|
+
* @returns {Invariant}
|
|
67
|
+
*/
|
|
68
|
+
createInvariant (args) {
|
|
69
|
+
// TODO: Validate that the args have the necessary keys
|
|
70
|
+
// and raise custom error if they are missing.
|
|
71
|
+
return new Invariant(args);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import DALEngineError from "./DALEngineError";
|
|
2
|
+
|
|
3
|
+
class InvalidTransitionError extends DALEngineError {
|
|
4
|
+
constructor (from, to) {
|
|
5
|
+
super(`Invalid transition from "${from}" to "${to}"`);
|
|
6
|
+
|
|
7
|
+
this.name = "InvalidTransitionError";
|
|
8
|
+
this.prevBehavior = from;
|
|
9
|
+
this.nextBehavior = to;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default InvalidTransitionError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import DALEngineError from "./DALEngineError";
|
|
2
|
+
|
|
3
|
+
class UnknownBehaviorError extends DALEngineError {
|
|
4
|
+
constructor (behaviorName) {
|
|
5
|
+
super(`The behavior named "${behaviorName}" was not found in graph.`);
|
|
6
|
+
|
|
7
|
+
this.name = "UnknownBehaviorError";
|
|
8
|
+
this.behaviorName = behaviorName;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default UnknownBehaviorError;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Base from "../Base";
|
|
2
|
+
import ENGINE_TYPES from "../TYPES";
|
|
3
|
+
import Participant from "./Participant";
|
|
4
|
+
/**
|
|
5
|
+
* Class representing a Behavior in the design.
|
|
6
|
+
*/
|
|
7
|
+
class Behavior extends Base {
|
|
8
|
+
/**
|
|
9
|
+
* Initialize the Behavior.
|
|
10
|
+
* @param {String} name
|
|
11
|
+
* @param args
|
|
12
|
+
*/
|
|
13
|
+
constructor (args) {
|
|
14
|
+
super();
|
|
15
|
+
this.type = ENGINE_TYPES.BEHAVIOR;
|
|
16
|
+
this.participants = [];
|
|
17
|
+
this.invalidWorldState = false;
|
|
18
|
+
if (typeof args === "object" && args !== null) {
|
|
19
|
+
if (Object.hasOwn(args, "uid")) {
|
|
20
|
+
this.loadBehaviorFromJSON(args);
|
|
21
|
+
} else {
|
|
22
|
+
this.name = args.name;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Loads the behavior from a JSON object.
|
|
29
|
+
* @param {Object} behaviorJSON
|
|
30
|
+
*/
|
|
31
|
+
loadBehaviorFromJSON (behaviorJSON) {
|
|
32
|
+
for (const [key, value] of Object.entries(behaviorJSON)) {
|
|
33
|
+
if (key === "participants") {
|
|
34
|
+
value.forEach(node => this.participants.push(new Participant(node)));
|
|
35
|
+
} else {
|
|
36
|
+
this[key] = behaviorJSON[key];
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Adds a participant to the behavior.
|
|
43
|
+
* @param {Participant} participant
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
addParticpant (participant) {
|
|
47
|
+
this.participants.push(participant);
|
|
48
|
+
return participant;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set the participant value.
|
|
53
|
+
* @param {String} participantName
|
|
54
|
+
* @param {*} value
|
|
55
|
+
*/
|
|
56
|
+
setParticipantValue (participantName, value) {
|
|
57
|
+
for (let i = 0; i < this.participants.length; i++) {
|
|
58
|
+
const participant = this.participants[i];
|
|
59
|
+
if (participant.name === participantName) {
|
|
60
|
+
participant.value = value;
|
|
61
|
+
const violation = participant.enforceInvariants();
|
|
62
|
+
if (violation) {
|
|
63
|
+
this.invalidWorldState = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default Behavior;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Base from "../Base";
|
|
2
|
+
import ENGINE_TYPES from "../TYPES";
|
|
3
|
+
/**
|
|
4
|
+
* Class representing a Invariant in the design.
|
|
5
|
+
*/
|
|
6
|
+
class Invariant extends Base {
|
|
7
|
+
/**
|
|
8
|
+
* Initialize the Invariant.
|
|
9
|
+
* @param {String} name
|
|
10
|
+
* @param args
|
|
11
|
+
*/
|
|
12
|
+
constructor (args) {
|
|
13
|
+
super();
|
|
14
|
+
this.type = ENGINE_TYPES.INVARIANT;
|
|
15
|
+
this.invariantViolated = false
|
|
16
|
+
if (typeof args === "object" && args !== null) {
|
|
17
|
+
if (Object.hasOwn(args, "uid")) {
|
|
18
|
+
this.loadInvariantFromJSON(args);
|
|
19
|
+
} else {
|
|
20
|
+
this.name = args.name;
|
|
21
|
+
this.rule = args.rule;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Loads the participant from a JSON object.
|
|
28
|
+
* @param {Object} invariantJSON
|
|
29
|
+
*/
|
|
30
|
+
loadInvariantFromJSON (invariantJSON) {
|
|
31
|
+
for (const [key, value] of Object.entries(invariantJSON)) {
|
|
32
|
+
this[key] = value;
|
|
33
|
+
};
|
|
34
|
+
// Reset these because they are set by the execution
|
|
35
|
+
this.invariantViolated = null;
|
|
36
|
+
this.value = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate the invariant.
|
|
41
|
+
* @param {*} value
|
|
42
|
+
* @returns {Boolean}
|
|
43
|
+
*/
|
|
44
|
+
evaluate (value) {
|
|
45
|
+
this.invariantViolated = false;
|
|
46
|
+
if (this.rule.type === "minLength") {
|
|
47
|
+
this.enforceMinLength(value);
|
|
48
|
+
}
|
|
49
|
+
return this.invariantViolated;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Enforce the string min length invariant
|
|
54
|
+
* @param value
|
|
55
|
+
*/
|
|
56
|
+
enforceMinLength (value) {
|
|
57
|
+
if ("keys" in this.rule) {
|
|
58
|
+
for (let i = 0; i < this.rule["keys"].length; i++) {
|
|
59
|
+
console.log(this.rule["keys"][i], value)
|
|
60
|
+
value = value[this.rule["keys"][i]];
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (value === null || typeof value !== "string" || value.length < this.rule.value) {
|
|
64
|
+
this.invariantViolated = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default Invariant;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import Base from "../Base";
|
|
2
|
+
import ENGINE_TYPES from "../TYPES";
|
|
3
|
+
import Invariant from "./Invariant";
|
|
4
|
+
/**
|
|
5
|
+
* Class representing a participant in the design.
|
|
6
|
+
*/
|
|
7
|
+
class Participant extends Base {
|
|
8
|
+
/**
|
|
9
|
+
* Initialize the semantic participant.
|
|
10
|
+
* @param {String} name
|
|
11
|
+
* @param args
|
|
12
|
+
*/
|
|
13
|
+
constructor (args) {
|
|
14
|
+
super();
|
|
15
|
+
this.type = ENGINE_TYPES.INVARIANT;
|
|
16
|
+
this.invariants = [];
|
|
17
|
+
this.invariantViolated = false;
|
|
18
|
+
if (typeof args === "object" && args !== null) {
|
|
19
|
+
if (Object.hasOwn(args, "uid")) {
|
|
20
|
+
this.loadParticipantFromJSON(args);
|
|
21
|
+
} else {
|
|
22
|
+
this.name = args.name;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Loads the participant from a JSON object.
|
|
29
|
+
* @param {Object} participantJSON
|
|
30
|
+
*/
|
|
31
|
+
loadParticipantFromJSON (participantJSON) {
|
|
32
|
+
for (const [key, value] of Object.entries(participantJSON)) {
|
|
33
|
+
if (key === "invariants") {
|
|
34
|
+
value.forEach(node => this.invariants.push(new Invariant(node)));
|
|
35
|
+
} else {
|
|
36
|
+
this[key] = participantJSON[key];
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Adds an invariant to the participant.
|
|
43
|
+
* @param {Invariant} invariant
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
addInvariant (invariant) {
|
|
47
|
+
this.invariants.push(invariant);
|
|
48
|
+
return invariant;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sets the value of the participant.
|
|
53
|
+
* @param {*} value
|
|
54
|
+
*/
|
|
55
|
+
setValue (value) {
|
|
56
|
+
this.value = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Enforces the particiants invariants.
|
|
61
|
+
* @returns {Boolean}
|
|
62
|
+
*/
|
|
63
|
+
enforceInvariants () {
|
|
64
|
+
this.invariantViolated = false
|
|
65
|
+
this.invariantViolationCount = 0;
|
|
66
|
+
for (let i = 0; i < this.invariants.length; i++) {
|
|
67
|
+
if (this.invariants[i].evaluate(this.value)) {
|
|
68
|
+
this.invariantViolated = true;
|
|
69
|
+
this.invariantViolationCount++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return this.invariantViolated;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default Participant;
|
package/src/TYPES.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {readFile, unlink, writeFile} from "fs/promises"
|
|
2
|
+
import {resolve} from "path"
|
|
3
|
+
import {describe, expect, it} from "vitest";
|
|
4
|
+
|
|
5
|
+
import {DALEngine} from "../src/DALEngine.js";
|
|
6
|
+
import InvalidTransitionError from "../src/Errors/InvalidTransitionError.js";
|
|
7
|
+
import UnknownBehaviorError from "../src/Errors/UnknownBehaviorError.js";
|
|
8
|
+
import ENGINE_TYPES from "../src/TYPES.js";
|
|
9
|
+
|
|
10
|
+
describe("DALEngine", () => {
|
|
11
|
+
it("sets the name correctly", () => {
|
|
12
|
+
const dalInstance = new DALEngine({name: "Library Manager"});
|
|
13
|
+
expect(dalInstance.name).toBe("Library Manager");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("adds node to graph", () => {
|
|
17
|
+
const d = new DALEngine({name: "Library Manager"});
|
|
18
|
+
const behavior1 = d.createBehavior({name: "AcceptBookFromUser"});
|
|
19
|
+
const behavior2 = d.createBehavior({name: "AddBookToBasket"});
|
|
20
|
+
const goToBehaviors = [behavior2];
|
|
21
|
+
|
|
22
|
+
const node = d.graph.addNode(behavior1, goToBehaviors)
|
|
23
|
+
|
|
24
|
+
const nodeType = node.type;
|
|
25
|
+
expect(nodeType).toBe(ENGINE_TYPES.GRAPH_NODE);
|
|
26
|
+
expect(node.behavior).toStrictEqual(behavior1);
|
|
27
|
+
expect(node.goToBehaviors).toStrictEqual([behavior2]);
|
|
28
|
+
|
|
29
|
+
const foundNode = d.graph.findNode("AcceptBookFromUser");
|
|
30
|
+
expect(foundNode).toStrictEqual(node);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("find node that was added using behavior name", () => {
|
|
34
|
+
const d = new DALEngine({name: "Library Manager"});
|
|
35
|
+
const behavior1 = d.createBehavior({name: "AcceptBookFromUser"});
|
|
36
|
+
const behavior2 = d.createBehavior({name: "AddBookToBasket"});
|
|
37
|
+
const node = d.graph.addNode(behavior1, [behavior2])
|
|
38
|
+
|
|
39
|
+
const foundNode = d.graph.findNode("AcceptBookFromUser");
|
|
40
|
+
expect(foundNode).toStrictEqual(node);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("find node and check if observed behavior is valid transition", () => {
|
|
44
|
+
const d = new DALEngine({name: "Library Manager"});
|
|
45
|
+
const behavior1 = d.createBehavior({name: "AcceptBookFromUser"});
|
|
46
|
+
const behavior2 = d.createBehavior({name: "AddBookToBasket"});
|
|
47
|
+
const behavior3 = d.createBehavior({name: "AnotherBehavior"});
|
|
48
|
+
d.graph.addNode(behavior1, [behavior2, behavior3])
|
|
49
|
+
d.graph.addNode(behavior2, [])
|
|
50
|
+
d.graph.addNode(behavior3, [])
|
|
51
|
+
|
|
52
|
+
// Misspell behavior name to trigger unknown behavior error
|
|
53
|
+
expect(() => {
|
|
54
|
+
d.graph.setCurrentBehavior("AcceptBookromUser");
|
|
55
|
+
}).toThrow(UnknownBehaviorError);
|
|
56
|
+
|
|
57
|
+
d.graph.setCurrentBehavior("AcceptBookFromUser");
|
|
58
|
+
expect(d.graph.currentNode.behavior).toBe(behavior1);
|
|
59
|
+
|
|
60
|
+
d.graph.goToBehavior("AddBookToBasket")
|
|
61
|
+
expect(d.graph.currentNode.behavior).toBe(behavior2);
|
|
62
|
+
|
|
63
|
+
// Reset current behavior so transition is valid
|
|
64
|
+
d.graph.setCurrentBehavior("AcceptBookFromUser");
|
|
65
|
+
d.graph.goToBehavior("AnotherBehavior")
|
|
66
|
+
expect(d.graph.currentNode.behavior).toBe(behavior3);
|
|
67
|
+
|
|
68
|
+
// Raises error because current behavior is "AnotherBehavior"
|
|
69
|
+
// and it does not transition to itself.
|
|
70
|
+
expect(() => {
|
|
71
|
+
d.graph.goToBehavior("AnotherBehavior")
|
|
72
|
+
}).toThrow(InvalidTransitionError);
|
|
73
|
+
|
|
74
|
+
// Reset the current behavior and then go to a behavior
|
|
75
|
+
// which is misspelled and expect an invalid transition error
|
|
76
|
+
expect(() => {
|
|
77
|
+
d.graph.setCurrentBehavior("AcceptBookFromUser");
|
|
78
|
+
d.graph.goToBehavior("AddBookToasket")
|
|
79
|
+
}).toThrow(InvalidTransitionError);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("add invariant to participant", () => {
|
|
83
|
+
const d = new DALEngine({name: "Library Manager"});
|
|
84
|
+
const book = d.createParticipant({name: "book"});
|
|
85
|
+
const invariant = d.createInvariant({name: "minLength"});
|
|
86
|
+
|
|
87
|
+
book.addInvariant(invariant);
|
|
88
|
+
|
|
89
|
+
const lastInvariant = book.invariants[book.invariants.length - 1];
|
|
90
|
+
expect(lastInvariant).toBe(invariant);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("serialize to file and deseralize from file", async () => {
|
|
94
|
+
let d = new DALEngine({name: "Library Manager"});
|
|
95
|
+
const book = d.createParticipant({name: "book"});
|
|
96
|
+
const invariant = d.createInvariant(
|
|
97
|
+
{
|
|
98
|
+
"name": "MinLengthConstraint",
|
|
99
|
+
"rule": {
|
|
100
|
+
"type": "minLength",
|
|
101
|
+
"keys": ["value", "name"],
|
|
102
|
+
"value": 1,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
book.addInvariant(invariant);
|
|
107
|
+
|
|
108
|
+
const behavior1 = d.createBehavior({name: "AcceptBookFromUser"});
|
|
109
|
+
behavior1.addParticpant(book);
|
|
110
|
+
const behavior2 = d.createBehavior({name: "AddBookToBasket"});
|
|
111
|
+
const behavior3 = d.createBehavior({name: "AnotherBehavior"});
|
|
112
|
+
d.graph.addNode(behavior1, [behavior2, behavior3]);
|
|
113
|
+
d.graph.addNode(behavior2, []);
|
|
114
|
+
d.graph.addNode(behavior3, []);
|
|
115
|
+
|
|
116
|
+
const filePath = resolve(__dirname, "./temp/inspectSerializeTemp.json")
|
|
117
|
+
await writeFile(filePath, d.serialize())
|
|
118
|
+
|
|
119
|
+
d = new DALEngine({name: "Library Manager"});
|
|
120
|
+
d.deserialize(await readFile(filePath, "utf-8"));
|
|
121
|
+
expect(d.graph.nodes.length).toBe(3);
|
|
122
|
+
|
|
123
|
+
// Intentionally not cleaning up the file because I want to inspect
|
|
124
|
+
// await unlink(filePath)
|
|
125
|
+
|
|
126
|
+
// This is a temporary file I create for my own inspection
|
|
127
|
+
const filePath2 = resolve(__dirname, "./temp/inspectDeseralizeTemp.json")
|
|
128
|
+
await writeFile(filePath2, d.serialize())
|
|
129
|
+
});
|
|
130
|
+
});
|