couchdb-ruby-server 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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