couchdb-ruby-server 1.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.
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+
3
+ module CouchdbRubyServer
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,8 @@
1
+ # coding: utf-8
2
+
3
+ require 'oj'
4
+ ::Oj.default_options = {:mode => :compat,
5
+ :class_cache => true }
6
+
7
+ require 'couchdb-ruby-server/version'
8
+ require 'couchdb-ruby-server/server'
@@ -0,0 +1,952 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
2
+ # use this file except in compliance with the License. You may obtain a copy of
3
+ # the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+ # License for the specific language governing permissions and limitations under
11
+ # the License.
12
+
13
+ # to run (requires ruby and rspec):
14
+ # rspec test/view_server/query_server_spec.rb
15
+ #
16
+ # environment options:
17
+ # QS_TRACE=true
18
+ # shows full output from the query server
19
+ # QS_LANG=lang
20
+ # run tests on the query server (for now, one of: js, erlang)
21
+ #
22
+ require 'json'
23
+
24
+ COUCH_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(COUCH_ROOT)
25
+ LANGUAGE = ENV["QS_LANG"] || "ruby"
26
+
27
+ puts "Running query server specs for #{LANGUAGE} query server"
28
+
29
+ require 'spec_helper'
30
+
31
+ class OSProcessRunner
32
+ def self.run
33
+ trace = ENV["QS_TRACE"] || false
34
+ puts "launching #{run_command}" if trace
35
+ if block_given?
36
+ IO.popen(run_command, "r+") do |io|
37
+ qs = QueryServerRunner.new(io, trace)
38
+ yield qs
39
+ end
40
+ else
41
+ io = IO.popen(run_command, "r+")
42
+ QueryServerRunner.new(io, trace)
43
+ end
44
+ end
45
+ def initialize io, trace = false
46
+ @qsio = io
47
+ @trace = trace
48
+ end
49
+ def close
50
+ @qsio.close
51
+ end
52
+ def reset!
53
+ run(["reset"])
54
+ end
55
+ def add_fun(fun)
56
+ run(["add_fun", fun])
57
+ end
58
+ def teach_ddoc(ddoc)
59
+ run(["ddoc", "new", ddoc_id(ddoc), ddoc])
60
+ end
61
+ def ddoc_run(ddoc, fun_path, args)
62
+ run(["ddoc", ddoc_id(ddoc), fun_path, args])
63
+ end
64
+ def ddoc_id(ddoc)
65
+ d_id = ddoc["_id"]
66
+ raise 'ddoc must have _id' unless d_id
67
+ d_id
68
+ end
69
+ def get_chunks
70
+ resp = jsgets
71
+ raise "not a chunk" unless resp.first == "chunks"
72
+ return resp[1]
73
+ end
74
+ def run json
75
+ rrun json
76
+ jsgets
77
+ end
78
+ def rrun json
79
+ line = json.to_json
80
+ puts "run: #{line}" if @trace
81
+ @qsio.puts line
82
+ end
83
+ def rgets
84
+ resp = @qsio.gets
85
+ puts "got: #{resp}" if @trace
86
+ resp
87
+ end
88
+ def jsgets
89
+ resp = rgets
90
+ # err = @qserr.gets
91
+ # puts "err: #{err}" if err
92
+ if resp
93
+ begin
94
+ rj = JSON.parse("[#{resp.chomp}]")[0]
95
+ rescue JSON::ParserError
96
+ puts "JSON ERROR (dump under trace mode)"
97
+ # puts resp.chomp
98
+ while resp = rgets
99
+ # puts resp.chomp
100
+ end
101
+ end
102
+ if rj.respond_to?(:[]) && rj.is_a?(Array)
103
+ if rj[0] == "log"
104
+ log = rj[1]
105
+ puts "log: #{log}" if @trace
106
+ rj = jsgets
107
+ end
108
+ end
109
+ rj
110
+ else
111
+ raise "no response"
112
+ end
113
+ end
114
+ end
115
+
116
+ class QueryServerRunner < OSProcessRunner
117
+
118
+ COMMANDS = {
119
+ "js" => "#{COUCH_ROOT}/bin/couchjs #{COUCH_ROOT}/share/server/main.js",
120
+ "erlang" => "#{COUCH_ROOT}/test/view_server/run_native_process.es",
121
+ "ruby" => "bin/couchdb-ruby-server"
122
+ }
123
+
124
+ def self.run_command
125
+ COMMANDS[LANGUAGE]
126
+ end
127
+ end
128
+
129
+ class ExternalRunner < OSProcessRunner
130
+ def self.run_command
131
+ "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/echo.js"
132
+ end
133
+ end
134
+
135
+ # we could organize this into a design document per language.
136
+ # that would make testing future languages really easy.
137
+
138
+ functions = {
139
+ "emit-twice" => {
140
+ "js" => %{function(doc){emit("foo",doc.a); emit("bar",doc.a)}},
141
+ "erlang" => <<-ERLANG,
142
+ fun({Doc}) ->
143
+ A = couch_util:get_value(<<"a">>, Doc, null),
144
+ Emit(<<"foo">>, A),
145
+ Emit(<<"bar">>, A)
146
+ end.
147
+ ERLANG
148
+ "ruby" => <<-RUBY
149
+ lambda {|doc| emit("foo",doc['a']); emit("bar",doc['a'])}
150
+ RUBY
151
+ },
152
+ "emit-once" => {
153
+ "js" => <<-JS,
154
+ function(doc){
155
+ emit("baz",doc.a)
156
+ }
157
+ JS
158
+ "erlang" => <<-ERLANG,
159
+ fun({Doc}) ->
160
+ A = couch_util:get_value(<<"a">>, Doc, null),
161
+ Emit(<<"baz">>, A)
162
+ end.
163
+ ERLANG
164
+ "ruby" => <<-RUBY
165
+ lambda {|doc| emit("baz", doc['a'])}
166
+ RUBY
167
+ },
168
+ "reduce-values-length" => {
169
+ "js" => %{function(keys, values, rereduce) { return values.length; }},
170
+ "erlang" => %{fun(Keys, Values, ReReduce) -> length(Values) end.},
171
+ "ruby" => %{lambda {|keys, values, rereduce| values.size }}
172
+ },
173
+ "reduce-values-sum" => {
174
+ "js" => %{function(keys, values, rereduce) { return sum(values); }},
175
+ "erlang" => %{fun(Keys, Values, ReReduce) -> lists:sum(Values) end.},
176
+ "ruby" => %{lambda {|keys, values, rereduce| values.reduce(0) {|sum, v| sum += v}}}
177
+ },
178
+ "validate-forbidden" => {
179
+ "js" => <<-JS,
180
+ function(newDoc, oldDoc, userCtx) {
181
+ if(newDoc.bad)
182
+ throw({forbidden:"bad doc"}); "foo bar";
183
+ }
184
+ JS
185
+ "erlang" => <<-ERLANG,
186
+ fun({NewDoc}, _OldDoc, _UserCtx) ->
187
+ case couch_util:get_value(<<"bad">>, NewDoc) of
188
+ undefined -> 1;
189
+ _ -> {[{forbidden, <<"bad doc">>}]}
190
+ end
191
+ end.
192
+ ERLANG
193
+ "ruby" => <<-RUBY
194
+ lambda do |newDoc, oldDoc, userCtx|
195
+ raise Error.new(:forbidden), 'bad doc' if newDoc['bad']
196
+ "foo bar"
197
+ end
198
+ RUBY
199
+ },
200
+ "show-simple" => {
201
+ "js" => <<-JS,
202
+ function(doc, req) {
203
+ log("ok");
204
+ return [doc.title, doc.body].join(' - ');
205
+ }
206
+ JS
207
+ "erlang" => <<-ERLANG,
208
+ fun({Doc}, Req) ->
209
+ Title = couch_util:get_value(<<"title">>, Doc),
210
+ Body = couch_util:get_value(<<"body">>, Doc),
211
+ Resp = <<Title/binary, " - ", Body/binary>>,
212
+ {[{<<"body">>, Resp}]}
213
+ end.
214
+ ERLANG
215
+ "ruby" => <<-RUBY
216
+ lambda { |doc, req|
217
+ log("ok");
218
+ [doc['title'], doc['body']].join(' - ');
219
+ }
220
+ RUBY
221
+ },
222
+ "show-headers" => {
223
+ "js" => <<-JS,
224
+ function(doc, req) {
225
+ var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}};
226
+ resp.body = [doc.title, doc.body].join(' - ');
227
+ return resp;
228
+ }
229
+ JS
230
+ "erlang" => <<-ERLANG,
231
+ fun({Doc}, Req) ->
232
+ Title = couch_util:get_value(<<"title">>, Doc),
233
+ Body = couch_util:get_value(<<"body">>, Doc),
234
+ Resp = <<Title/binary, " - ", Body/binary>>,
235
+ {[
236
+ {<<"code">>, 200},
237
+ {<<"headers">>, {[{<<"X-Plankton">>, <<"Rusty">>}]}},
238
+ {<<"body">>, Resp}
239
+ ]}
240
+ end.
241
+ ERLANG
242
+ "ruby" => <<-RUBY
243
+ lambda { |doc, req|
244
+ resp = {:code=>200, :headers=>{"X-Plankton"=>"Rusty"}};
245
+ resp[:body] = [doc['title'], doc['body']].join(' - ');
246
+ resp
247
+ }
248
+ RUBY
249
+ },
250
+ "show-sends" => {
251
+ "js" => <<-JS,
252
+ function(head, req) {
253
+ start({headers:{"Content-Type" : "text/plain"}});
254
+ send("first chunk");
255
+ send('second "chunk"');
256
+ return "tail";
257
+ };
258
+ JS
259
+ "erlang" => <<-ERLANG,
260
+ fun(Head, Req) ->
261
+ Resp = {[
262
+ {<<"headers">>, {[{<<"Content-Type">>, <<"text/plain">>}]}}
263
+ ]},
264
+ Start(Resp),
265
+ Send(<<"first chunk">>),
266
+ Send(<<"second \\\"chunk\\\"">>),
267
+ <<"tail">>
268
+ end.
269
+ ERLANG
270
+ "ruby" => <<-RUBY
271
+ lambda { |head, req|
272
+ start({:headers => {"Content-Type" => "text/plain"}});
273
+ send("first chunk");
274
+ send('second "chunk"');
275
+ "tail"
276
+ }
277
+ RUBY
278
+ },
279
+ "show-while-get-rows" => {
280
+ "js" => <<-JS,
281
+ function(head, req) {
282
+ send("first chunk");
283
+ send(req.q);
284
+ var row;
285
+ log("about to getRow " + typeof(getRow));
286
+ while(row = getRow()) {
287
+ send(row.key);
288
+ };
289
+ return "tail";
290
+ };
291
+ JS
292
+ "erlang" => <<-ERLANG,
293
+ fun(Head, {Req}) ->
294
+ Send(<<"first chunk">>),
295
+ Send(couch_util:get_value(<<"q">>, Req)),
296
+ Fun = fun({Row}, _) ->
297
+ Send(couch_util:get_value(<<"key">>, Row)),
298
+ {ok, nil}
299
+ end,
300
+ {ok, _} = FoldRows(Fun, nil),
301
+ <<"tail">>
302
+ end.
303
+ ERLANG
304
+ "ruby" => <<-RUBY
305
+ lambda { |head, req|
306
+ send("first chunk");
307
+ send(req['q']);
308
+ while(row = get_row)
309
+ send(row['key']);
310
+ end
311
+ "tail";
312
+ }
313
+ RUBY
314
+ },
315
+ "show-while-get-rows-multi-send" => {
316
+ "js" => <<-JS,
317
+ function(head, req) {
318
+ send("bacon");
319
+ var row;
320
+ log("about to getRow " + typeof(getRow));
321
+ while(row = getRow()) {
322
+ send(row.key);
323
+ send("eggs");
324
+ };
325
+ return "tail";
326
+ };
327
+ JS
328
+ "erlang" => <<-ERLANG,
329
+ fun(Head, Req) ->
330
+ Send(<<"bacon">>),
331
+ Fun = fun({Row}, _) ->
332
+ Send(couch_util:get_value(<<"key">>, Row)),
333
+ Send(<<"eggs">>),
334
+ {ok, nil}
335
+ end,
336
+ FoldRows(Fun, nil),
337
+ <<"tail">>
338
+ end.
339
+ ERLANG
340
+ "ruby" => <<-RUBY
341
+ lambda do |head, req|
342
+ send("bacon");
343
+ while(row = get_row)
344
+ send(row['key']);
345
+ send("eggs");
346
+ end
347
+ "tail";
348
+ end
349
+ RUBY
350
+ },
351
+ "list-simple" => {
352
+ "js" => <<-JS,
353
+ function(head, req) {
354
+ send("first chunk");
355
+ send(req.q);
356
+ var row;
357
+ while(row = getRow()) {
358
+ send(row.key);
359
+ };
360
+ return "early";
361
+ };
362
+ JS
363
+ "erlang" => <<-ERLANG,
364
+ fun(Head, {Req}) ->
365
+ Send(<<"first chunk">>),
366
+ Send(couch_util:get_value(<<"q">>, Req)),
367
+ Fun = fun({Row}, _) ->
368
+ Send(couch_util:get_value(<<"key">>, Row)),
369
+ {ok, nil}
370
+ end,
371
+ FoldRows(Fun, nil),
372
+ <<"early">>
373
+ end.
374
+ ERLANG
375
+ "ruby" => <<-RUBY
376
+ lambda do |head, req|
377
+ send("first chunk");
378
+ send(req['q']);
379
+ while(row = get_row)
380
+ send(row['key']);
381
+ end
382
+ "early";
383
+ end
384
+ RUBY
385
+ },
386
+ "list-chunky" => {
387
+ "js" => <<-JS,
388
+ function(head, req) {
389
+ send("first chunk");
390
+ send(req.q);
391
+ var row, i=0;
392
+ while(row = getRow()) {
393
+ send(row.key);
394
+ i += 1;
395
+ if (i > 2) {
396
+ return('early tail');
397
+ }
398
+ };
399
+ };
400
+ JS
401
+ "erlang" => <<-ERLANG,
402
+ fun(Head, {Req}) ->
403
+ Send(<<"first chunk">>),
404
+ Send(couch_util:get_value(<<"q">>, Req)),
405
+ Fun = fun
406
+ ({Row}, Count) when Count < 2 ->
407
+ Send(couch_util:get_value(<<"key">>, Row)),
408
+ {ok, Count+1};
409
+ ({Row}, Count) when Count == 2 ->
410
+ Send(couch_util:get_value(<<"key">>, Row)),
411
+ {stop, <<"early tail">>}
412
+ end,
413
+ {ok, Tail} = FoldRows(Fun, 0),
414
+ Tail
415
+ end.
416
+ ERLANG
417
+ "ruby" => <<-RUBY
418
+ lambda do |head, req|
419
+ send("first chunk");
420
+ send(req['q']);
421
+ i=0
422
+ while(row = get_row)
423
+ send(row['key']);
424
+ i += 1;
425
+ return 'early tail' if i > 2
426
+ end
427
+ end
428
+ RUBY
429
+ },
430
+ "list-old-style" => {
431
+ "js" => <<-JS,
432
+ function(head, req, foo, bar) {
433
+ return "stuff";
434
+ }
435
+ JS
436
+ "erlang" => <<-ERLANG,
437
+ fun(Head, Req, Foo, Bar) ->
438
+ <<"stuff">>
439
+ end.
440
+ ERLANG
441
+ "ruby" => <<-RUBY
442
+ lambda do |head, req, foo, bar|
443
+ send "stuff"
444
+ end
445
+ RUBY
446
+ },
447
+ "list-capped" => {
448
+ "js" => <<-JS,
449
+ function(head, req) {
450
+ send("bacon")
451
+ var row, i = 0;
452
+ while(row = getRow()) {
453
+ send(row.key);
454
+ i += 1;
455
+ if (i > 2) {
456
+ return('early');
457
+ }
458
+ };
459
+ }
460
+ JS
461
+ "erlang" => <<-ERLANG,
462
+ fun(Head, Req) ->
463
+ Send(<<"bacon">>),
464
+ Fun = fun
465
+ ({Row}, Count) when Count < 2 ->
466
+ Send(couch_util:get_value(<<"key">>, Row)),
467
+ {ok, Count+1};
468
+ ({Row}, Count) when Count == 2 ->
469
+ Send(couch_util:get_value(<<"key">>, Row)),
470
+ {stop, <<"early">>}
471
+ end,
472
+ {ok, Tail} = FoldRows(Fun, 0),
473
+ Tail
474
+ end.
475
+ ERLANG
476
+ "ruby" => <<-RUBY
477
+ lambda do |head, req|
478
+ send("bacon")
479
+ i = 0;
480
+ while(row = get_row)
481
+ send(row['key']);
482
+ i += 1;
483
+ return 'early' if i > 2
484
+ end
485
+ end
486
+ RUBY
487
+ },
488
+ "list-raw" => {
489
+ "js" => <<-JS,
490
+ function(head, req) {
491
+ // log(this.toSource());
492
+ // log(typeof send);
493
+ send("first chunk");
494
+ send(req.q);
495
+ var row;
496
+ while(row = getRow()) {
497
+ send(row.key);
498
+ };
499
+ return "tail";
500
+ };
501
+ JS
502
+ "erlang" => <<-ERLANG,
503
+ fun(Head, {Req}) ->
504
+ Send(<<"first chunk">>),
505
+ Send(couch_util:get_value(<<"q">>, Req)),
506
+ Fun = fun({Row}, _) ->
507
+ Send(couch_util:get_value(<<"key">>, Row)),
508
+ {ok, nil}
509
+ end,
510
+ FoldRows(Fun, nil),
511
+ <<"tail">>
512
+ end.
513
+ ERLANG
514
+ "ruby" => <<-RUBY
515
+ lambda do |head, req|
516
+ # log(this.toSource());
517
+ # log(typeof send);
518
+ send("first chunk");
519
+ send(req['q']);
520
+ while(row = get_row)
521
+ send(row['key']);
522
+ end
523
+ "tail";
524
+ end
525
+ RUBY
526
+ },
527
+ "filter-basic" => {
528
+ "js" => <<-JS,
529
+ function(doc, req) {
530
+ if (doc.good) {
531
+ return true;
532
+ }
533
+ }
534
+ JS
535
+ "erlang" => <<-ERLANG,
536
+ fun({Doc}, Req) ->
537
+ couch_util:get_value(<<"good">>, Doc)
538
+ end.
539
+ ERLANG
540
+ "ruby" => <<-RUBY
541
+ lambda { |doc, req|
542
+ doc['good']
543
+ }
544
+ RUBY
545
+ },
546
+ "update-basic" => {
547
+ "js" => <<-JS,
548
+ function(doc, req) {
549
+ doc.world = "hello";
550
+ var resp = [doc, "hello doc"];
551
+ return resp;
552
+ }
553
+ JS
554
+ "erlang" => <<-ERLANG,
555
+ fun({Doc}, Req) ->
556
+ Doc2 = [{<<"world">>, <<"hello">>}|Doc],
557
+ [{Doc2}, {[{<<"body">>, <<"hello doc">>}]}]
558
+ end.
559
+ ERLANG
560
+ "ruby" => <<-RUBY
561
+ lambda { |doc, req|
562
+ doc['world'] = "hello";
563
+ resp = [doc, "hello doc"];
564
+ return resp;
565
+ }
566
+ RUBY
567
+ },
568
+ "error" => {
569
+ "js" => <<-JS,
570
+ function() {
571
+ throw(["error","error_key","testing"]);
572
+ }
573
+ JS
574
+ "erlang" => <<-ERLANG,
575
+ fun(A, B) ->
576
+ throw([<<"error">>,<<"error_key">>,<<"testing">>])
577
+ end.
578
+ ERLANG
579
+ "ruby" => <<-RUBY
580
+ lambda { |*args|
581
+ raise Error.new(:error_key), "testing"
582
+ }
583
+ RUBY
584
+ },
585
+ "fatal" => {
586
+ "js" => <<-JS,
587
+ function() {
588
+ throw(["fatal","error_key","testing"]);
589
+ }
590
+ JS
591
+ "erlang" => <<-ERLANG,
592
+ fun(A, B) ->
593
+ throw([<<"fatal">>,<<"error_key">>,<<"testing">>])
594
+ end.
595
+ ERLANG
596
+ "ruby" => <<-RUBY
597
+ lambda { |*args|
598
+ raise Fatal.new(:error_key), "testing"
599
+ }
600
+ RUBY
601
+ }
602
+ }
603
+
604
+ def make_ddoc(fun_path, fun_str)
605
+ doc = {"_id"=>"foo"}
606
+ d = doc
607
+ while p = fun_path.shift
608
+ l = p
609
+ if !fun_path.empty?
610
+ d[p] = {}
611
+ d = d[p]
612
+ end
613
+ end
614
+ d[l] = fun_str
615
+ doc
616
+ end
617
+
618
+ describe "query server normal case" do
619
+ before(:all) do
620
+ `cd #{COUCH_ROOT} && make` unless LANGUAGE == 'ruby'
621
+ @qs = QueryServerRunner.run
622
+ end
623
+ after(:all) do
624
+ @qs.close
625
+ end
626
+ it "should reset" do
627
+ @qs.run(["reset"]).must_equal true
628
+ end
629
+ it "should not erase ddocs on reset" do
630
+ @fun = functions["show-simple"][LANGUAGE]
631
+ @ddoc = make_ddoc(["shows","simple"], @fun)
632
+ @qs.teach_ddoc(@ddoc)
633
+ @qs.run(["reset"]).must_equal true
634
+ @qs.ddoc_run(@ddoc,
635
+ ["shows","simple"],
636
+ [{:title => "Best ever", :body => "Doc body"}, {}]).
637
+ must_equal ["resp", {"body" => "Best ever - Doc body"}]
638
+ end
639
+
640
+ it "should run map funs" do
641
+ @qs.reset!
642
+ @qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).must_equal true
643
+ @qs.run(["add_fun", functions["emit-once"][LANGUAGE]]).must_equal true
644
+ rows = @qs.run(["map_doc", {:a => "b"}])
645
+ rows[0][0].must_equal ["foo", "b"]
646
+ rows[0][1].must_equal ["bar", "b"]
647
+ rows[1][0].must_equal ["baz", "b"]
648
+ end
649
+ describe "reduce" do
650
+ before(:all) do
651
+ @fun = functions["reduce-values-length"][LANGUAGE]
652
+ @qs.reset!
653
+ end
654
+ it "should reduce" do
655
+ kvs = (0...10).collect{|i|[i,i*2]}
656
+ @qs.run(["reduce", [@fun], kvs]).must_equal [true, [10]]
657
+ end
658
+ end
659
+ describe "rereduce" do
660
+ before(:all) do
661
+ @fun = functions["reduce-values-sum"][LANGUAGE]
662
+ @qs.reset!
663
+ end
664
+ it "should rereduce" do
665
+ vs = (0...10).collect{|i|i}
666
+ @qs.run(["rereduce", [@fun], vs]).must_equal [true, [45]]
667
+ end
668
+ end
669
+
670
+ describe "design docs" do
671
+ before(:all) do
672
+ @ddoc = {
673
+ "_id" => "foo"
674
+ }
675
+ @qs.reset!
676
+ end
677
+ it "should learn design docs" do
678
+ @qs.teach_ddoc(@ddoc).must_equal true
679
+ end
680
+ end
681
+
682
+ # it "should validate"
683
+ describe "validation" do
684
+ before(:all) do
685
+ @fun = functions["validate-forbidden"][LANGUAGE]
686
+ @ddoc = make_ddoc(["validate_doc_update"], @fun)
687
+ @qs.teach_ddoc(@ddoc)
688
+ end
689
+ it "should allow good updates" do
690
+ @qs.ddoc_run(@ddoc,
691
+ ["validate_doc_update"],
692
+ [{"good" => true}, {}, {}]).must_equal 1
693
+ end
694
+ it "should reject invalid updates" do
695
+ @qs.ddoc_run(@ddoc,
696
+ ["validate_doc_update"],
697
+ [{"bad" => true}, {}, {}]).must_equal({"forbidden"=>"bad doc"})
698
+ end
699
+ end
700
+
701
+ describe "show" do
702
+ before(:all) do
703
+ @fun = functions["show-simple"][LANGUAGE]
704
+ @ddoc = make_ddoc(["shows","simple"], @fun)
705
+ @qs.teach_ddoc(@ddoc)
706
+ end
707
+ it "should show" do
708
+ @qs.ddoc_run(@ddoc,
709
+ ["shows","simple"],
710
+ [{:title => "Best ever", :body => "Doc body"}, {}]).
711
+ must_equal ["resp", {"body" => "Best ever - Doc body"}]
712
+ end
713
+ end
714
+
715
+ describe "show with headers" do
716
+ before(:all) do
717
+ # TODO we can make real ddocs up there.
718
+ @fun = functions["show-headers"][LANGUAGE]
719
+ @ddoc = make_ddoc(["shows","headers"], @fun)
720
+ @qs.teach_ddoc(@ddoc)
721
+ end
722
+ it "should show headers" do
723
+ @qs.ddoc_run(
724
+ @ddoc,
725
+ ["shows","headers"],
726
+ [{:title => "Best ever", :body => "Doc body"}, {}]
727
+ ).
728
+ must_equal ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}]
729
+ end
730
+ end
731
+
732
+ describe "recoverable error" do
733
+ before(:all) do
734
+ @fun = functions["error"][LANGUAGE]
735
+ @ddoc = make_ddoc(["shows","error"], @fun)
736
+ @qs.teach_ddoc(@ddoc)
737
+ end
738
+ it "should not exit" do
739
+ @qs.ddoc_run(@ddoc, ["shows","error"],
740
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
741
+ must_equal ["error", "error_key", "testing"]
742
+ # still running
743
+ @qs.run(["reset"]).must_equal true
744
+ end
745
+ end
746
+
747
+ describe "changes filter" do
748
+ before(:all) do
749
+ @fun = functions["filter-basic"][LANGUAGE]
750
+ @ddoc = make_ddoc(["filters","basic"], @fun)
751
+ @qs.teach_ddoc(@ddoc)
752
+ end
753
+ it "should only return true for good docs" do
754
+ @qs.ddoc_run(@ddoc,
755
+ ["filters","basic"],
756
+ [[{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]
757
+ ).
758
+ must_equal [true, [true, false, true]]
759
+ end
760
+ end
761
+
762
+ describe "update" do
763
+ before(:all) do
764
+ # in another patch we can remove this duplication
765
+ # by setting up the design doc for each language ahead of time.
766
+ @fun = functions["update-basic"][LANGUAGE]
767
+ @ddoc = make_ddoc(["updates","basic"], @fun)
768
+ @qs.teach_ddoc(@ddoc)
769
+ end
770
+ it "should return a doc and a resp body" do
771
+ up, doc, resp = @qs.ddoc_run(@ddoc,
772
+ ["updates","basic"],
773
+ [{"foo" => "gnarly"}, {"method" => "POST"}]
774
+ )
775
+ up.must_equal "up"
776
+ doc.must_equal({"foo" => "gnarly", "world" => "hello"})
777
+ resp["body"].must_equal "hello doc"
778
+ end
779
+ end
780
+
781
+ # end
782
+ # LIST TESTS
783
+ # __END__
784
+
785
+ describe "ddoc list" do
786
+ before(:all) do
787
+ @ddoc = {
788
+ "_id" => "foo",
789
+ "lists" => {
790
+ "simple" => functions["list-simple"][LANGUAGE],
791
+ "headers" => functions["show-sends"][LANGUAGE],
792
+ "rows" => functions["show-while-get-rows"][LANGUAGE],
793
+ "buffer-chunks" => functions["show-while-get-rows-multi-send"][LANGUAGE],
794
+ "chunky" => functions["list-chunky"][LANGUAGE]
795
+ }
796
+ }
797
+ @qs.teach_ddoc(@ddoc)
798
+ end
799
+
800
+ describe "example list" do
801
+ it "should run normal" do
802
+ @qs.ddoc_run(@ddoc,
803
+ ["lists","simple"],
804
+ [{"foo"=>"bar"}, {"q" => "ok"}]
805
+ ).must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
806
+ @qs.run(["list_row", {"key"=>"baz"}]).must_equal ["chunks", ["baz"]]
807
+ @qs.run(["list_row", {"key"=>"bam"}]).must_equal ["chunks", ["bam"]]
808
+ @qs.run(["list_row", {"key"=>"foom"}]).must_equal ["chunks", ["foom"]]
809
+ @qs.run(["list_row", {"key"=>"fooz"}]).must_equal ["chunks", ["fooz"]]
810
+ @qs.run(["list_row", {"key"=>"foox"}]).must_equal ["chunks", ["foox"]]
811
+ @qs.run(["list_end"]).must_equal ["end" , ["early"]]
812
+ end
813
+ end
814
+
815
+ describe "headers" do
816
+ it "should do headers proper" do
817
+ @qs.ddoc_run(@ddoc, ["lists","headers"],
818
+ [{"total_rows"=>1000}, {"q" => "ok"}]
819
+ ).must_equal ["start", ["first chunk", 'second "chunk"'],
820
+ {"headers"=>{"Content-Type"=>"text/plain"}}]
821
+ @qs.rrun(["list_end"])
822
+ @qs.jsgets.must_equal ["end", ["tail"]]
823
+ end
824
+ end
825
+
826
+ describe "with rows" do
827
+ it "should list em" do
828
+ @qs.ddoc_run(@ddoc, ["lists","rows"],
829
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
830
+ must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
831
+ @qs.rrun(["list_row", {"key"=>"baz"}])
832
+ @qs.get_chunks.must_equal ["baz"]
833
+ @qs.rrun(["list_row", {"key"=>"bam"}])
834
+ @qs.get_chunks.must_equal ["bam"]
835
+ @qs.rrun(["list_end"])
836
+ @qs.jsgets.must_equal ["end", ["tail"]]
837
+ end
838
+ it "should work with zero rows" do
839
+ @qs.ddoc_run(@ddoc, ["lists","rows"],
840
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
841
+ must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
842
+ @qs.rrun(["list_end"])
843
+ @qs.jsgets.must_equal ["end", ["tail"]]
844
+ end
845
+ end
846
+
847
+ describe "should buffer multiple chunks sent for a single row." do
848
+ it "should should buffer em" do
849
+ @qs.ddoc_run(@ddoc, ["lists","buffer-chunks"],
850
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
851
+ must_equal ["start", ["bacon"], {"headers"=>{}}]
852
+ @qs.rrun(["list_row", {"key"=>"baz"}])
853
+ @qs.get_chunks.must_equal ["baz", "eggs"]
854
+ @qs.rrun(["list_row", {"key"=>"bam"}])
855
+ @qs.get_chunks.must_equal ["bam", "eggs"]
856
+ @qs.rrun(["list_end"])
857
+ @qs.jsgets.must_equal ["end", ["tail"]]
858
+ end
859
+ end
860
+ it "should end after 2" do
861
+ @qs.ddoc_run(@ddoc, ["lists","chunky"],
862
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
863
+ must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
864
+
865
+ @qs.run(["list_row", {"key"=>"baz"}]).
866
+ must_equal ["chunks", ["baz"]]
867
+
868
+ @qs.run(["list_row", {"key"=>"bam"}]).
869
+ must_equal ["chunks", ["bam"]]
870
+
871
+ @qs.run(["list_row", {"key"=>"foom"}]).
872
+ must_equal ["end", ["foom", "early tail"]]
873
+ # here's where js has to discard quit properly
874
+ @qs.run(["reset"]).
875
+ must_equal true
876
+ end
877
+ end
878
+ end
879
+
880
+
881
+
882
+ def should_have_exited qs
883
+ begin
884
+ qs.run(["reset"])
885
+ "raise before this (except Erlang)".must_equal true
886
+ rescue RuntimeError => e
887
+ e.message.must_equal "no response"
888
+ rescue Errno::EPIPE
889
+ true.must_equal true
890
+ end
891
+ end
892
+
893
+ describe "query server that exits" do
894
+ before(:each) do
895
+ @qs = QueryServerRunner.run
896
+ @ddoc = {
897
+ "_id" => "foo",
898
+ "lists" => {
899
+ "capped" => functions["list-capped"][LANGUAGE],
900
+ "raw" => functions["list-raw"][LANGUAGE]
901
+ },
902
+ "shows" => {
903
+ "fatal" => functions["fatal"][LANGUAGE]
904
+ }
905
+ }
906
+ @qs.teach_ddoc(@ddoc)
907
+ end
908
+ after(:each) do
909
+ @qs.close
910
+ end
911
+
912
+ describe "only goes to 2 list" do
913
+ it "should exit if erlang sends too many rows" do
914
+ @qs.ddoc_run(@ddoc, ["lists","capped"],
915
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
916
+ must_equal ["start", ["bacon"], {"headers"=>{}}]
917
+ @qs.run(["list_row", {"key"=>"baz"}]).must_equal ["chunks", ["baz"]]
918
+ @qs.run(["list_row", {"key"=>"foom"}]).must_equal ["chunks", ["foom"]]
919
+ @qs.run(["list_row", {"key"=>"fooz"}]).must_equal ["end", ["fooz", "early"]]
920
+ e = @qs.run(["list_row", {"key"=>"foox"}])
921
+ e[0].must_equal "error"
922
+ e[1].must_equal "unknown_command"
923
+ should_have_exited @qs
924
+ end
925
+ end
926
+
927
+ describe "raw list" do
928
+ it "should exit if it gets a non-row in the middle" do
929
+ @qs.ddoc_run(@ddoc, ["lists","raw"],
930
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
931
+ must_equal ["start", ["first chunk", "ok"], {"headers"=>{}}]
932
+ e = @qs.run(["reset"])
933
+ e[0].must_equal "error"
934
+ e[1].must_equal "list_error"
935
+ should_have_exited @qs
936
+ end
937
+ end
938
+
939
+ describe "fatal error" do
940
+ it "should exit" do
941
+ @qs.ddoc_run(@ddoc, ["shows","fatal"],
942
+ [{"foo"=>"bar"}, {"q" => "ok"}]).
943
+ must_equal ["error", "error_key", "testing"]
944
+ should_have_exited @qs
945
+ end
946
+ end
947
+ end
948
+
949
+ describe "thank you for using the tests" do
950
+ it "for more info run with QS_TRACE=true or see query_server_spec.rb file header" do
951
+ end
952
+ end