1 /// Test for [tagion.services.hirpc_verifier]_
2 module tagion.testbench.services.hirpc_verifier;
3 // Default import list for bdd
4 import core.time;
5 import std.stdio;
6 import std.typecons : Tuple;
7 import tagion.actor;
8 import tagion.actor.exceptions;
9 import tagion.behaviour;
10 import tagion.communication.HiRPC;
11 import tagion.crypto.SecureNet;
12 import tagion.hibon.Document;
13 import tagion.hibon.HiBON;
14 import tagion.services.hirpc_verifier;
15 import tagion.services.messages;
16 import tagion.testbench.actor.util;
17 import tagion.testbench.tools.Environment;
18 import tagion.utils.pretend_safe_concurrency;
19 
20 enum feature = Feature(
21             "HiRPCInterfaceService.",
22             [
23         "The transaction service should be able to receive HiRPC, validate data format and protocol rules before it sends to and send to the Collector services.",
24         "The HiRPC is package into a HiBON-Document in the following called doc."
25 ]);
26 
27 alias FeatureContext = Tuple!(
28         TheDocumentIsNotAHiRPC, "TheDocumentIsNotAHiRPC",
29         CorrectHiRPCFormatAndPermission, "CorrectHiRPCFormatAndPermission",
30         CorrectHiRPCWithPermissionDenied, "CorrectHiRPCWithPermissionDenied",
31         HIRPCWithIllegalMethod, "HIRPCWithIllegalMethod",
32         FeatureGroup*, "result"
33 );
34 
35 @safe @Scenario("The Document is not a HiRPC",
36         [])
37 class TheDocumentIsNotAHiRPC {
38     ActorHandle hirpc_verifier_handle;
39     string hirpc_verifier_success;
40     string hirpc_verifier_reject;
41     this(ActorHandle _hirpc_verifier_handle, string _success, string _reject) {
42         hirpc_verifier_handle = _hirpc_verifier_handle;
43         hirpc_verifier_success = _success; // The name of the service which successfull documents are sent to
44         hirpc_verifier_reject = _reject; // The name of the service which rejected documents are sent to
45     }
46 
47     Document doc;
48 
49     @Given("a doc with a correct document format but which is incorrect HiRPC format.")
50     Document format() {
51         writeln(thisTid);
52         check(waitforChildren(Ctrl.ALIVE), "hirpc_verifierService never alived");
53         check(hirpc_verifier_handle.tid !is Tid.init, "hirpc_verifier thread is not running");
54 
55         auto hibon = new HiBON();
56         hibon["$test"] = 5;
57         doc = Document(hibon);
58         hirpc_verifier_handle.send(inputDoc(), doc);
59 
60         return result_ok;
61     }
62 
63     @When("the doc should be received by the this services.")
64     Document services() {
65         return result_ok;
66     }
67 
68     @Then("the doc should be checked that it is a correct HiRPC and if it is not it should be rejected.")
69     Document rejected() {
70         const receiveTuple = receiveOnlyTimeout!(RejectReason, Document);
71         check(receiveTuple[0] == RejectReason.notAHiRPC, "Did not reject for the correct reason");
72         check(receiveTuple[1] == doc, "The rejected doc was not the same as was sent");
73         return result_ok;
74     }
75 
76     @But("the doc should not be sent to the Collector Service")
77     Document collectorService() {
78         return result_ok;
79     }
80 
81 }
82 
83 @safe @Scenario("Correct HiRPC format and permission.",
84         [
85     "The #permission scenario can be executed with and without correct permission."
86 ])
87 class CorrectHiRPCFormatAndPermission {
88     ActorHandle hirpc_verifier_handle;
89     string contract_success;
90     string contract_reject;
91     HiRPC hirpc;
92     this(ActorHandle _hirpc_verifier_handle, string _success, string _reject) {
93         hirpc_verifier_handle = _hirpc_verifier_handle;
94         contract_success = _success; // The name of the service which successfull documents are sent to
95         contract_reject = _reject; // The name of the service which rejected documents are sent to
96         hirpc = HiRPC(new HiRPCNet("someObscurePassphrase"));
97     }
98 
99     class HiRPCNet : StdSecureNet {
100         this(string passphrase) {
101             super();
102             generateKeyPair(passphrase);
103         }
104     }
105 
106     Document doc;
107 
108     @Given("a correctly formatted transaction.")
109     Document transaction() {
110         import std.algorithm : map;
111         import std.array;
112         import std.range : iota;
113         import tagion.basic.Types : Buffer;
114         import tagion.crypto.Types;
115         import tagion.script.TagionCurrency;
116         import tagion.script.common;
117         import tagion.utils.StdTime;
118 
119         writeln(thisTid);
120         check(waitforChildren(Ctrl.ALIVE), "ContractService never alived");
121         check(hirpc_verifier_handle.tid !is Tid.init, "Contract thread is not running");
122 
123         Document[] in_bills;
124         in_bills ~= iota(0, 10).map!(_ => TagionBill(10.TGN, sdt_t.init, Pubkey.init, Buffer.init).toDoc).array;
125         immutable(TagionBill)[] out_bills;
126         out_bills ~= iota(0, 10).map!(_ => TagionBill(5.TGN, sdt_t.init, Pubkey.init, Buffer.init)).array;
127         auto contract = immutable(Contract)(null, null, PayScript(out_bills).toDoc);
128         SignedContract signed_contract = SignedContract(null, contract);
129 
130         const sender = hirpc.submit(signed_contract);
131         doc = sender.toDoc;
132         hirpc_verifier_handle.send(inputDoc(), doc);
133 
134         return result_ok;
135     }
136 
137     // All of these are invisible to the user,
138     // The test specification should be changed to reflect this
139     @When("the doc package has been verified that it is correct Document.")
140     Document document() {
141         return result_ok;
142     }
143 
144     @When("the doc package has been verified that it is correct HiRPC.")
145     Document hiRPC() {
146         return result_ok;
147     }
148 
149     @Then("the method of HiRPC should be checked that it is \'submit\'.")
150     Document submit() {
151         return result_ok;
152     }
153 
154     @Then("the parameter for the send to the Collector service.")
155     Document service() {
156         return result_ok;
157     }
158 
159     @Then("if check that the Collector services received the contract.")
160     Document contract() {
161         const receiver = receiveOnlyTimeout!(inputHiRPC, immutable(HiRPC.Receiver))()[1];
162         check(receiver.method.name == ContractMethods.submit, "The incorrect method name was sent back");
163         check(receiver.toDoc == doc, "The received sender was not the same as was sent");
164 
165         return result_ok;
166     }
167 }
168 
169 @safe @Scenario("Correct HiRPC with permission denied.",
170         [])
171 class CorrectHiRPCWithPermissionDenied {
172 
173     ActorHandle hirpc_verifier_handle;
174     string hirpc_verifier_success;
175     string hirpc_verifier_reject;
176     HiRPC bad_hirpc;
177     this(ActorHandle _hirpc_verifier_handle, string _success, string _reject) {
178         hirpc_verifier_handle = _hirpc_verifier_handle;
179         hirpc_verifier_success = _success; // The name of the service which successfull documents are sent to
180         hirpc_verifier_reject = _reject; // The name of the service which rejected documents are sent to
181         bad_hirpc = HiRPC(new BadSecureNet("someLessObscurePassphrase"));
182     }
183 
184     Document invalid_doc;
185     @Given("a HiPRC with incorrect permission")
186     Document incorrectPermission() {
187         check(waitforChildren(Ctrl.ALIVE), "hirpc_verifierService never alived");
188         check(hirpc_verifier_handle.tid !is Tid.init, "hirpc_verifier thread is not running");
189         auto params = new HiBON;
190         params["test"] = 42;
191         const invalid_sender = bad_hirpc.action(ContractMethods.submit, params);
192         invalid_doc = invalid_sender.toDoc;
193         hirpc_verifier_handle.send(inputDoc(), invalid_doc);
194         return result_ok;
195     }
196 
197     @When("do scenario \'#permission\'")
198     Document scenarioPermission() {
199         const receiveTuple = receiveOnlyTimeout!(RejectReason, Document);
200         check(receiveTuple[0] == RejectReason.invalidType, "The docuemnt was not rejected for the correct reason");
201         check(receiveTuple[1] == invalid_doc, "The rejected doc was not the same as was sent");
202 
203         return result_ok;
204     }
205 
206     @Then("check that the contract is not send to the Collector.")
207     Document theCollector() {
208         receiveTimeout(
209                 Duration.zero,
210                 (inputHiRPC _, HiRPC.Sender __) { check(false, "Should not have received a doc"); },
211         );
212 
213         return result_ok;
214     }
215 
216 }
217 
218 @safe @Scenario("A hirpc with an illegal type", [])
219 class HIRPCWithIllegalMethod {
220     ActorHandle hirpc_verifier_handle;
221     string contract_success;
222     string contract_reject;
223     HiRPC hirpc;
224     SecureNet net;
225     this(ActorHandle _hirpc_verifier_handle, string _success, string _reject) {
226         hirpc_verifier_handle = _hirpc_verifier_handle;
227         contract_success = _success; // The name of the service which successfull documents are sent to
228         contract_reject = _reject; // The name of the service which rejected documents are sent to
229 
230         net = new HiRPCNet("someObscurePassphrase");
231         hirpc = HiRPC(net);
232     }
233 
234     class HiRPCNet : StdSecureNet {
235         this(string passphrase) {
236             super();
237             generateKeyPair(passphrase);
238         }
239     }
240 
241     Document invalid_doc;
242 
243     @Given("i send HiRPC receiver")
244     Document transaction() {
245         import std.algorithm : map;
246         import std.array;
247         import std.range : iota;
248         import tagion.basic.Types : Buffer;
249         import tagion.crypto.Types;
250         import tagion.script.TagionCurrency;
251         import tagion.script.common;
252         import tagion.utils.StdTime;
253 
254         writeln(thisTid);
255         check(waitforChildren(Ctrl.ALIVE), "ContractService never alived");
256         check(hirpc_verifier_handle.tid !is Tid.init, "Contract thread is not running");
257 
258         Document[] in_bills;
259         in_bills ~= iota(0, 10).map!(_ => TagionBill(10.TGN, sdt_t.init, Pubkey.init, Buffer.init).toDoc).array;
260         TagionBill[] out_bills;
261         out_bills ~= iota(0, 10).map!(_ => TagionBill(5.TGN, sdt_t.init, Pubkey.init, Buffer.init)).array;
262         auto contract = Contract(null, null, PayScript(out_bills).toDoc);
263         SignedContract signed_contract = SignedContract(null, contract);
264 
265         const sender = hirpc.submit(signed_contract);
266 
267         HiRPC.Response message;
268         message.id = sender.method.id;
269         message.result = sender.toDoc;
270         const result = HiRPC.Sender(net, message);
271         invalid_doc = result.toDoc;
272 
273         hirpc_verifier_handle.send(inputDoc(), invalid_doc);
274 
275         return result_ok;
276     }
277 
278     @Then("Then it should be rejected")
279     Document contract() {
280         receiveTimeout(
281                 Duration.zero,
282                 (inputHiRPC _, HiRPC.Sender __) { check(false, "Should not have received a doc"); },
283         );
284 
285         const receiveTuple = receiveOnlyTimeout!(RejectReason, Document);
286         check(receiveTuple[0] == RejectReason.invalidType, "The docuemnt was not rejected for the correct reason");
287         check(receiveTuple[1] == invalid_doc, "The rejected doc was not the same as was sent");
288 
289         hirpc_verifier_handle.send(Sig.STOP);
290         check(waitforChildren(Ctrl.END), "hirpc verifier service Never ended");
291 
292         return result_ok;
293     }
294 }