1 /// Test for [tagion.services.inputvalidator]
2 module tagion.testbench.services.inputvalidator;
3 // Default import list for bdd
4 import core.time;
5 import nngd;
6 import std.format;
7 import std.stdio;
8 import std.typecons : Tuple;
9 import std.typecons;
10 import tagion.actor;
11 import tagion.actor.exceptions;
12 import tagion.basic.Types;
13 import tagion.behaviour;
14 import tagion.communication.HiRPC;
15 import tagion.hibon.Document;
16 import tagion.hibon.HiBON;
17 import tagion.hibon.HiBONBase;
18 import tagion.hibon.HiBONJSON;
19 import tagion.logger.LogRecords : LogInfo;
20 import tagion.logger.Logger;
21 import tagion.services.inputvalidator;
22 import tagion.services.messages;
23 import tagion.testbench.actor.util;
24 import tagion.testbench.tools.Environment;
25 import tagion.tools.Basic;
26 import concurrency = tagion.utils.pretend_safe_concurrency;
27 import tagion.utils.pretend_safe_concurrency;
28 
29 enum feature = Feature(
30             "Inputvalidator service",
31             [
32             "This feature should verify that the inputvalidator accepts valid and rejects invalid LEB128 input over a socket"
33             ]);
34 
35 alias FeatureContext = Tuple!(
36         SendADocumentToTheSocket, "SendADocumentToTheSocket",
37         SendNoneHiRPC, "SendNoneHiRPC",
38         SendPartialHiBON, "SendPartialHiBON",
39         SendBigContract, "SendBigContract",
40         FeatureGroup*, "result"
41 );
42 
43 @safe @Scenario("send a HiRPC document to the socket", [])
44 class SendADocumentToTheSocket {
45     NNGSocket sock;
46     const string sock_path;
47     this(string _sock_path) @trusted {
48         sock = NNGSocket(nng_socket_type.NNG_SOCKET_REQ);
49         sock_path = _sock_path;
50     }
51 
52     Document doc;
53 
54     @Given("a inputvalidator")
55     Document aInputvalidator() {
56         waitforChildren(Ctrl.ALIVE);
57         return result_ok;
58     }
59 
60     @When("we send a HiRPC `Document`")
61     Document aSocket() @trusted {
62         sock.sendtimeout = msecs(1000);
63         sock.sendbuf = 4096;
64         sock.recvbuf = 4096;
65         int rc = sock.dial(sock_path /* nonblock : true */ );
66         check(rc == 0, format("Failed to dial %s", nng_errstr(rc)));
67         HiRPC hirpc;
68         auto hibon = new HiBON();
69         hibon["$test"] = 5;
70         const sender = hirpc.act(hibon);
71         doc = sender.toDoc;
72         rc = sock.send(doc.serialize);
73         check(rc == 0, format("Failed to send %s", nng_errstr(rc)));
74         Document received = sock.receive!Buffer;
75         check(sock.m_errno == 0, format("Failed to receive %s", nng_errstr(sock.m_errno)));
76         check(received.length != 0, "Received empty doc");
77         auto receiver = hirpc.receive(received);
78         check(receiver.isResponse, "Expected an error");
79 
80         return result_ok;
81     }
82 
83     @Then("we receive back the Document in our mailbox")
84     Document ourMailbox() @trusted {
85         auto res = concurrency.receiveOnly!(Tuple!(inputDoc, Document));
86         writeln("Receive back: ", res[1].toPretty);
87         check(res[1] == doc, "The value was not the same as we sent");
88         return result_ok;
89     }
90 }
91 
92 @safe @Scenario("send none hirpc document", [])
93 class SendNoneHiRPC {
94 
95     NNGSocket sock;
96     const string sock_path;
97     this(string _sock_path) @trusted {
98         sock = NNGSocket(nng_socket_type.NNG_SOCKET_REQ);
99         sock_path = _sock_path;
100     }
101 
102     @Given("a inputvalidator")
103     Document inputvalidator() {
104         waitforChildren(Ctrl.ALIVE);
105 
106         register("inputvalidator_tester", thisTid);
107 
108         log.registerSubscriptionTask("inputvalidator_tester");
109         submask.subscribe(InputValidatorService.rejected);
110         return result_ok;
111     }
112 
113     @When("we send a document which is not a HiRPC on a socket")
114     Document socket() @trusted {
115         sock.sendtimeout = msecs(1000);
116         sock.sendbuf = 4096;
117         sock.recvbuf = 4096;
118         sock.recvtimeout = msecs(1000);
119         int rc = sock.dial(sock_path);
120         check(rc == 0, format("Failed to dial %s", rc));
121 
122         auto hibon = new HiBON();
123         hibon["$test"] = 5;
124         writefln("Buf length %s %s", hibon.serialize.length, Document(hibon.serialize).valid);
125 
126         rc = sock.send(hibon.serialize);
127         check(rc == 0, format("Failed to send %s", rc));
128         Document received = sock.receive!Buffer;
129         check(sock.m_errno == 0, format("Failed to receive %s", nng_errstr(sock.m_errno)));
130         check(received.length != 0, "Received empty buffer");
131         check(received !is Document.init, "Received empty document");
132         HiRPC hirpc = HiRPC(null);
133         auto receiver = hirpc.receive(received);
134         check(receiver.isError, "Expected an error");
135 
136         return result_ok;
137     }
138 
139     @Then("the inputvalidator rejects")
140     Document rejects() {
141         import tagion.testbench.actor.util;
142 
143         check(!concurrency.receiveTimeout(100.msecs, (inputDoc _, Document __) {}),
144                 "should not have received a doc");
145         receiveOnlyTimeout!(LogInfo, const(Document));
146 
147         return result_ok;
148     }
149 
150 }
151 
152 @safe @Scenario("send partial HiBON", [])
153 class SendPartialHiBON {
154 
155     NNGSocket sock;
156     const string sock_path;
157     this(string _sock_path) @trusted {
158         sock = NNGSocket(nng_socket_type.NNG_SOCKET_REQ);
159         sock_path = _sock_path;
160         sock.sendtimeout = msecs(1000);
161         sock.sendbuf = 4096;
162     }
163 
164     @Given("a inputvalidator")
165     Document inputvalidator() {
166         check(waitforChildren(Ctrl.ALIVE), "waitforChildren");
167 
168         register("inputvalidator_tester", thisTid);
169         log.registerSubscriptionTask("inputvalidator_tester");
170         submask.subscribe(InputValidatorService.rejected);
171         return result_ok;
172     }
173 
174     @When("we send a `partial_hibon` on a socket")
175     Document socket() @trusted {
176         int rc = sock.dial(sock_path);
177         check(rc == 0, format("Failed to dial %s", nng_errstr(rc)));
178         HiRPC hirpc;
179         auto hibon = new HiBON();
180         hibon["$test"] = 5;
181         const sender = hirpc.act(hibon);
182         Document doc = sender.toDoc;
183         immutable partial_buf = doc.serialize[0 .. 26].dup;
184         writefln("Buf length %s %s", partial_buf.length, Document(partial_buf).valid);
185         rc = sock.send(partial_buf);
186         check(rc == 0, format("Failed to send %s", nng_errstr(rc)));
187         Document received = sock.receive!Buffer;
188         check(sock.m_errno == 0, format("Failed to receive %s", nng_errstr(sock.m_errno)));
189         check(received.length != 0, "Received empty doc");
190         auto receiver = hirpc.receive(received);
191         check(receiver.isError, "Expected an error");
192 
193         return result_ok;
194     }
195 
196     @Then("the inputvalidator rejects")
197     Document rejects() {
198         check(!concurrency.receiveTimeout(100.msecs, (inputDoc _, Document __) {}), "should not have received a doc");
199         receiveOnlyTimeout!(LogInfo, const(Document)); // Subscribed rejected data
200         return result_ok;
201     }
202 
203 }
204 
205 @safe @Scenario("send Big Contract",
206         [])
207 class SendBigContract {
208 
209     NNGSocket sock;
210     const string sock_path;
211     this(string _sock_path) @trusted {
212         sock = NNGSocket(nng_socket_type.NNG_SOCKET_REQ);
213         sock_path = _sock_path;
214         sock.sendtimeout = msecs(1000);
215         sock.sendbuf = 0x4000;
216     }
217 
218 
219     @Given("a inputvalidator")
220     Document inputvalidator() {
221         check(waitforChildren(Ctrl.ALIVE), "waitforChildren");
222 
223         register("inputvalidator_tester", thisTid);
224         log.registerSubscriptionTask("inputvalidator_tester");
225         submask.subscribe(InputValidatorService.rejected);
226         return result_ok;
227     }
228 
229     @When("we send a `huge contract` on a socket")
230     Document socket() @trusted {
231         import std.range;
232         import std.algorithm;
233 
234         HiRPC hirpc;
235         int rc = sock.dial(sock_path);
236         check(rc == 0, format("Failed to dial %s", nng_errstr(rc)));
237         auto hibon = new HiBON();
238 
239         string long_string = iota(0,10_000).map!(i => format("%d", i)).join;
240         // writefln(long_string);
241         hibon["$test"] = long_string;
242         const sender = hirpc.act(hibon);
243         auto to_send = sender.toDoc.serialize;
244         writefln("long_hibon length=%skb", to_send.length/1000, Document(to_send).valid);
245         rc = sock.send(to_send);
246         check(rc == 0, format("Failed to send %s", nng_errstr(rc)));
247         Document received = sock.receive!Buffer;
248         check(sock.m_errno == 0, format("Failed to receive %s", nng_errstr(sock.m_errno)));
249         check(received.length != 0, "Received empty doc");
250         auto receiver = hirpc.receive(received);
251         writefln(receiver.toPretty);
252         check(!receiver.isError, "Did not expect an error");
253         
254         return result_ok;
255     }
256 
257     @Then("we should receive response ok")
258     Document ok() {
259         check(concurrency.receiveTimeout(200.msecs, (inputDoc _, Document __) {}), "should have received a doc");
260         return result_ok;
261     }
262 
263 }