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
23COUCH_ROOT = "#{File.dirname(__FILE__)}/../.." unless defined?(COUCH_ROOT)
24LANGUAGE = ENV["QS_LANG"] || "js"
25
26puts "Running query server specs for #{LANGUAGE} query server"
27
28require 'rspec'
29require 'json'
30
31class 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
114end
115
116class 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  }
122
123  def self.run_command
124    COMMANDS[LANGUAGE]
125  end
126end
127
128class ExternalRunner < OSProcessRunner
129  def self.run_command
130    "#{COUCH_ROOT}/src/couchdb/couchjs #{COUCH_ROOT}/share/server/echo.js"
131  end
132end
133
134# we could organize this into a design document per language.
135# that would make testing future languages really easy.
136
137functions = {
138  "emit-twice" => {
139    "js" => %{function(doc){emit("foo",doc.a); emit("bar",doc.a)}},
140    "erlang" => <<-ERLANG
141      fun({Doc}) ->
142        A = couch_util:get_value(<<"a">>, Doc, null),
143        Emit(<<"foo">>, A),
144        Emit(<<"bar">>, A)
145      end.
146    ERLANG
147  },
148  "emit-once" => {
149    "js" => <<-JS,
150      function(doc){
151        emit("baz",doc.a)
152      }
153      JS
154    "erlang" => <<-ERLANG
155        fun({Doc}) ->
156            A = couch_util:get_value(<<"a">>, Doc, null),
157            Emit(<<"baz">>, A)
158        end.
159    ERLANG
160  },
161  "reduce-values-length" => {
162    "js" => %{function(keys, values, rereduce) { return values.length; }},
163    "erlang" => %{fun(Keys, Values, ReReduce) -> length(Values) end.}
164  },
165  "reduce-values-sum" => {
166    "js" => %{function(keys, values, rereduce) { return sum(values); }},
167    "erlang" => %{fun(Keys, Values, ReReduce) -> lists:sum(Values) end.}
168  },
169  "validate-forbidden" => {
170    "js" => <<-JS,
171      function(newDoc, oldDoc, userCtx) {
172        if(newDoc.bad)
173          throw({forbidden:"bad doc"}); "foo bar";
174      }
175      JS
176    "erlang" => <<-ERLANG
177      fun({NewDoc}, _OldDoc, _UserCtx) ->
178        case couch_util:get_value(<<"bad">>, NewDoc) of
179            undefined -> 1;
180            _ -> {[{forbidden, <<"bad doc">>}]}
181        end
182      end.
183    ERLANG
184  },
185  "show-simple" => {
186    "js" => <<-JS,
187        function(doc, req) {
188            log("ok");
189            return [doc.title, doc.body].join(' - ');
190        }
191    JS
192    "erlang" => <<-ERLANG
193      fun({Doc}, Req) ->
194            Title = couch_util:get_value(<<"title">>, Doc),
195            Body = couch_util:get_value(<<"body">>, Doc),
196            Resp = <<Title/binary, " - ", Body/binary>>,
197        {[{<<"body">>, Resp}]}
198      end.
199    ERLANG
200  },
201  "show-headers" => {
202    "js" => <<-JS,
203        function(doc, req) {
204          var resp = {"code":200, "headers":{"X-Plankton":"Rusty"}};
205          resp.body = [doc.title, doc.body].join(' - ');
206          return resp;
207        }
208     JS
209    "erlang" => <<-ERLANG
210  fun({Doc}, Req) ->
211        Title = couch_util:get_value(<<"title">>, Doc),
212        Body = couch_util:get_value(<<"body">>, Doc),
213        Resp = <<Title/binary, " - ", Body/binary>>,
214        {[
215        {<<"code">>, 200},
216        {<<"headers">>, {[{<<"X-Plankton">>, <<"Rusty">>}]}},
217        {<<"body">>, Resp}
218      ]}
219  end.
220    ERLANG
221  },
222  "show-sends" => {
223    "js" =>  <<-JS,
224        function(head, req) {
225          start({headers:{"Content-Type" : "text/plain"}});
226          send("first chunk");
227          send('second "chunk"');
228          return "tail";
229        };
230    JS
231    "erlang" => <<-ERLANG
232      fun(Head, Req) ->
233        Resp = {[
234          {<<"headers">>, {[{<<"Content-Type">>, <<"text/plain">>}]}}
235        ]},
236        Start(Resp),
237        Send(<<"first chunk">>),
238        Send(<<"second \\\"chunk\\\"">>),
239        <<"tail">>
240      end.
241    ERLANG
242  },
243  "show-while-get-rows" => {
244    "js" =>  <<-JS,
245        function(head, req) {
246          send("first chunk");
247          send(req.q);
248          var row;
249          log("about to getRow " + typeof(getRow));
250          while(row = getRow()) {
251            send(row.key);
252          };
253          return "tail";
254        };
255    JS
256    "erlang" => <<-ERLANG,
257        fun(Head, {Req}) ->
258            Send(<<"first chunk">>),
259            Send(couch_util:get_value(<<"q">>, Req)),
260            Fun = fun({Row}, _) ->
261                Send(couch_util:get_value(<<"key">>, Row)),
262                {ok, nil}
263            end,
264            {ok, _} = FoldRows(Fun, nil),
265            <<"tail">>
266        end.
267    ERLANG
268  },
269  "show-while-get-rows-multi-send" => {
270    "js" => <<-JS,
271        function(head, req) {
272          send("bacon");
273          var row;
274          log("about to getRow " + typeof(getRow));
275          while(row = getRow()) {
276            send(row.key);
277            send("eggs");
278          };
279          return "tail";
280        };
281    JS
282    "erlang" => <<-ERLANG,
283        fun(Head, Req) ->
284            Send(<<"bacon">>),
285            Fun = fun({Row}, _) ->
286                Send(couch_util:get_value(<<"key">>, Row)),
287                Send(<<"eggs">>),
288                {ok, nil}
289            end,
290            FoldRows(Fun, nil),
291            <<"tail">>
292        end.
293    ERLANG
294  },
295  "list-simple" => {
296    "js" => <<-JS,
297        function(head, req) {
298          send("first chunk");
299          send(req.q);
300          var row;
301          while(row = getRow()) {
302            send(row.key);
303          };
304          return "early";
305        };
306    JS
307    "erlang" => <<-ERLANG,
308        fun(Head, {Req}) ->
309            Send(<<"first chunk">>),
310            Send(couch_util:get_value(<<"q">>, Req)),
311            Fun = fun({Row}, _) ->
312                Send(couch_util:get_value(<<"key">>, Row)),
313                {ok, nil}
314            end,
315            FoldRows(Fun, nil),
316            <<"early">>
317        end.
318    ERLANG
319  },
320  "list-chunky" => {
321    "js" => <<-JS,
322        function(head, req) {
323          send("first chunk");
324          send(req.q);
325          var row, i=0;
326          while(row = getRow()) {
327            send(row.key);
328            i += 1;
329            if (i > 2) {
330              return('early tail');
331            }
332          };
333        };
334    JS
335    "erlang" => <<-ERLANG,
336        fun(Head, {Req}) ->
337            Send(<<"first chunk">>),
338            Send(couch_util:get_value(<<"q">>, Req)),
339            Fun = fun
340                ({Row}, Count) when Count < 2 ->
341                    Send(couch_util:get_value(<<"key">>, Row)),
342                    {ok, Count+1};
343                ({Row}, Count) when Count == 2 ->
344                    Send(couch_util:get_value(<<"key">>, Row)),
345                    {stop, <<"early tail">>}
346            end,
347            {ok, Tail} = FoldRows(Fun, 0),
348            Tail
349        end.
350    ERLANG
351  },
352  "list-old-style" => {
353    "js" => <<-JS,
354        function(head, req, foo, bar) {
355          return "stuff";
356        }
357    JS
358    "erlang" => <<-ERLANG,
359        fun(Head, Req, Foo, Bar) ->
360            <<"stuff">>
361        end.
362    ERLANG
363  },
364  "list-capped" => {
365    "js" => <<-JS,
366        function(head, req) {
367          send("bacon")
368          var row, i = 0;
369          while(row = getRow()) {
370            send(row.key);
371            i += 1;
372            if (i > 2) {
373              return('early');
374            }
375          };
376        }
377    JS
378    "erlang" => <<-ERLANG,
379        fun(Head, Req) ->
380            Send(<<"bacon">>),
381            Fun = fun
382                ({Row}, Count) when Count < 2 ->
383                    Send(couch_util:get_value(<<"key">>, Row)),
384                    {ok, Count+1};
385                ({Row}, Count) when Count == 2 ->
386                    Send(couch_util:get_value(<<"key">>, Row)),
387                    {stop, <<"early">>}
388            end,
389            {ok, Tail} = FoldRows(Fun, 0),
390            Tail
391        end.
392    ERLANG
393  },
394  "list-raw" => {
395    "js" => <<-JS,
396        function(head, req) {
397          // log(this.toSource());
398          // log(typeof send);
399          send("first chunk");
400          send(req.q);
401          var row;
402          while(row = getRow()) {
403            send(row.key);
404          };
405          return "tail";
406        };
407    JS
408    "erlang" => <<-ERLANG,
409        fun(Head, {Req}) ->
410            Send(<<"first chunk">>),
411            Send(couch_util:get_value(<<"q">>, Req)),
412            Fun = fun({Row}, _) ->
413                Send(couch_util:get_value(<<"key">>, Row)),
414                {ok, nil}
415            end,
416            FoldRows(Fun, nil),
417            <<"tail">>
418        end.
419    ERLANG
420  },
421  "filter-basic" => {
422    "js" => <<-JS,
423      function(doc, req) {
424        if (doc.good) {
425          return true;
426        }
427      }
428    JS
429    "erlang" => <<-ERLANG,
430        fun({Doc}, Req) ->
431            couch_util:get_value(<<"good">>, Doc)
432        end.
433    ERLANG
434  },
435  "update-basic" => {
436    "js" => <<-JS,
437    function(doc, req) {
438      doc.world = "hello";
439      var resp = [doc, "hello doc"];
440      return resp;
441    }
442    JS
443    "erlang" => <<-ERLANG,
444        fun({Doc}, Req) ->
445            Doc2 = [{<<"world">>, <<"hello">>}|Doc],
446            [{Doc2}, {[{<<"body">>, <<"hello doc">>}]}]
447        end.
448    ERLANG
449  },
450  "rewrite-basic" => {
451    "js" => <<-JS,
452    function(req) {
453      return "new/location";
454    }
455    JS
456    "erlang" => <<-ERLANG,
457        fun(Req) ->
458            {[{"path", "new/location"}]}
459        end.
460    ERLANG
461  },
462  "rewrite-no-rule" => {
463    "js" => <<-JS,
464    function(req) {
465      return;
466    }
467    JS
468    "erlang" => <<-ERLANG,
469        fun(Req) ->
470            undefined
471        end.
472    ERLANG
473  },
474  "error" => {
475    "js" => <<-JS,
476    function() {
477      throw(["error","error_key","testing"]);
478    }
479    JS
480    "erlang" => <<-ERLANG
481    fun(A, B) ->
482      throw([<<"error">>,<<"error_key">>,<<"testing">>])
483    end.
484    ERLANG
485  },
486  "fatal" => {
487    "js" => <<-JS,
488    function() {
489      throw(["fatal","error_key","testing"]);
490    }
491    JS
492    "erlang" => <<-ERLANG
493    fun(A, B) ->
494      throw([<<"fatal">>,<<"error_key">>,<<"testing">>])
495    end.
496    ERLANG
497  }
498}
499
500def make_ddoc(fun_path, fun_str)
501  doc = {"_id"=>"foo"}
502  d = doc
503  while p = fun_path.shift
504    l = p
505    if !fun_path.empty?
506      d[p] = {}
507      d = d[p]
508    end
509  end
510  d[l] = fun_str
511  doc
512end
513
514describe "query server normal case" do
515  before(:all) do
516    `cd #{COUCH_ROOT} && make`
517    @qs = QueryServerRunner.run
518  end
519  after(:all) do
520    @qs.close
521  end
522  it "should reset" do
523    @qs.run(["reset"]).should == true
524  end
525  it "should not erase ddocs on reset" do
526    @fun = functions["show-simple"][LANGUAGE]
527    @ddoc = make_ddoc(["shows","simple"], @fun)
528    @qs.teach_ddoc(@ddoc)
529    @qs.run(["reset"]).should == true
530    @qs.ddoc_run(@ddoc,
531      ["shows","simple"],
532      [{:title => "Best ever", :body => "Doc body"}, {}]).should ==
533    ["resp", {"body" => "Best ever - Doc body"}]
534  end
535
536  it "should run map funs" do
537    @qs.reset!
538    @qs.run(["add_fun", functions["emit-twice"][LANGUAGE]]).should == true
539    @qs.run(["add_fun", functions["emit-once"][LANGUAGE]]).should == true
540    rows = @qs.run(["map_doc", {:a => "b"}])
541    rows[0][0].should == ["foo", "b"]
542    rows[0][1].should == ["bar", "b"]
543    rows[1][0].should == ["baz", "b"]
544  end
545  describe "reduce" do
546    before(:all) do
547      @fun = functions["reduce-values-length"][LANGUAGE]
548      @qs.reset!
549    end
550    it "should reduce" do
551      kvs = (0...10).collect{|i|[i,i*2]}
552      @qs.run(["reduce", [@fun], kvs]).should == [true, [10]]
553    end
554  end
555  describe "rereduce" do
556    before(:all) do
557      @fun = functions["reduce-values-sum"][LANGUAGE]
558      @qs.reset!
559    end
560    it "should rereduce" do
561      vs = (0...10).collect{|i|i}
562      @qs.run(["rereduce", [@fun], vs]).should == [true, [45]]
563    end
564  end
565
566  describe "design docs" do
567    before(:all) do
568      @ddoc = {
569        "_id" => "foo"
570      }
571      @qs.reset!
572    end
573    it "should learn design docs" do
574      @qs.teach_ddoc(@ddoc).should == true
575    end
576  end
577
578  # it "should validate"
579  describe "validation" do
580    before(:all) do
581      @fun = functions["validate-forbidden"][LANGUAGE]
582      @ddoc = make_ddoc(["validate_doc_update"], @fun)
583      @qs.teach_ddoc(@ddoc)
584    end
585    it "should allow good updates" do
586      @qs.ddoc_run(@ddoc,
587        ["validate_doc_update"],
588        [{"good" => true}, {}, {}]).should == 1
589    end
590    it "should reject invalid updates" do
591      @qs.ddoc_run(@ddoc,
592        ["validate_doc_update"],
593        [{"bad" => true}, {}, {}]).should == {"forbidden"=>"bad doc"}
594    end
595  end
596
597  describe "show" do
598    before(:all) do
599      @fun = functions["show-simple"][LANGUAGE]
600      @ddoc = make_ddoc(["shows","simple"], @fun)
601      @qs.teach_ddoc(@ddoc)
602    end
603    it "should show" do
604      @qs.ddoc_run(@ddoc,
605        ["shows","simple"],
606        [{:title => "Best ever", :body => "Doc body"}, {}]).should ==
607      ["resp", {"body" => "Best ever - Doc body"}]
608    end
609  end
610
611  describe "show with headers" do
612    before(:all) do
613      # TODO we can make real ddocs up there.
614      @fun = functions["show-headers"][LANGUAGE]
615      @ddoc = make_ddoc(["shows","headers"], @fun)
616      @qs.teach_ddoc(@ddoc)
617    end
618    it "should show headers" do
619      @qs.ddoc_run(
620        @ddoc,
621        ["shows","headers"],
622        [{:title => "Best ever", :body => "Doc body"}, {}]
623      ).
624      should == ["resp", {"code"=>200,"headers" => {"X-Plankton"=>"Rusty"}, "body" => "Best ever - Doc body"}]
625    end
626  end
627
628  describe "recoverable error" do
629    before(:all) do
630      @fun = functions["error"][LANGUAGE]
631      @ddoc = make_ddoc(["shows","error"], @fun)
632      @qs.teach_ddoc(@ddoc)
633    end
634    it "should not exit" do
635      @qs.ddoc_run(@ddoc, ["shows","error"],
636        [{"foo"=>"bar"}, {"q" => "ok"}]).
637        should == ["error", "error_key", "testing"]
638      # still running
639      @qs.run(["reset"]).should == true
640    end
641  end
642
643  describe "changes filter" do
644    before(:all) do
645      @fun = functions["filter-basic"][LANGUAGE]
646      @ddoc = make_ddoc(["filters","basic"], @fun)
647      @qs.teach_ddoc(@ddoc)
648    end
649    it "should only return true for good docs" do
650      @qs.ddoc_run(@ddoc,
651        ["filters","basic"],
652        [[{"key"=>"bam", "good" => true}, {"foo" => "bar"}, {"good" => true}], {"req" => "foo"}]
653      ).
654      should == [true, [true, false, true]]
655    end
656  end
657
658  describe "update" do
659    before(:all) do
660      # in another patch we can remove this duplication
661      # by setting up the design doc for each language ahead of time.
662      @fun = functions["update-basic"][LANGUAGE]
663      @ddoc = make_ddoc(["updates","basic"], @fun)
664      @qs.teach_ddoc(@ddoc)
665    end
666    it "should return a doc and a resp body" do
667      up, doc, resp = @qs.ddoc_run(@ddoc,
668        ["updates","basic"],
669        [{"foo" => "gnarly"}, {"method" => "POST"}]
670      )
671      up.should == "up"
672      doc.should == {"foo" => "gnarly", "world" => "hello"}
673      resp["body"].should == "hello doc"
674    end
675  end
676
677# end
678#                    LIST TESTS
679# __END__
680
681  describe "ddoc list" do
682      before(:all) do
683        @ddoc = {
684          "_id" => "foo",
685          "lists" => {
686            "simple" => functions["list-simple"][LANGUAGE],
687            "headers" => functions["show-sends"][LANGUAGE],
688            "rows" => functions["show-while-get-rows"][LANGUAGE],
689            "buffer-chunks" => functions["show-while-get-rows-multi-send"][LANGUAGE],
690            "chunky" => functions["list-chunky"][LANGUAGE]
691          }
692        }
693        @qs.teach_ddoc(@ddoc)
694      end
695
696      describe "example list" do
697        it "should run normal" do
698          @qs.ddoc_run(@ddoc,
699            ["lists","simple"],
700            [{"foo"=>"bar"}, {"q" => "ok"}]
701          ).should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
702          @qs.run(["list_row", {"key"=>"baz"}]).should ==  ["chunks", ["baz"]]
703          @qs.run(["list_row", {"key"=>"bam"}]).should ==  ["chunks", ["bam"]]
704          @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
705          @qs.run(["list_row", {"key"=>"fooz"}]).should == ["chunks", ["fooz"]]
706          @qs.run(["list_row", {"key"=>"foox"}]).should == ["chunks", ["foox"]]
707          @qs.run(["list_end"]).should == ["end" , ["early"]]
708        end
709      end
710
711      describe "headers" do
712        it "should do headers proper" do
713          @qs.ddoc_run(@ddoc, ["lists","headers"],
714            [{"total_rows"=>1000}, {"q" => "ok"}]
715          ).should == ["start", ["first chunk", 'second "chunk"'],
716            {"headers"=>{"Content-Type"=>"text/plain"}}]
717          @qs.rrun(["list_end"])
718          @qs.jsgets.should == ["end", ["tail"]]
719        end
720      end
721
722      describe "with rows" do
723        it "should list em" do
724          @qs.ddoc_run(@ddoc, ["lists","rows"],
725            [{"foo"=>"bar"}, {"q" => "ok"}]).
726            should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
727          @qs.rrun(["list_row", {"key"=>"baz"}])
728          @qs.get_chunks.should == ["baz"]
729          @qs.rrun(["list_row", {"key"=>"bam"}])
730          @qs.get_chunks.should == ["bam"]
731          @qs.rrun(["list_end"])
732          @qs.jsgets.should == ["end", ["tail"]]
733        end
734        it "should work with zero rows" do
735          @qs.ddoc_run(@ddoc, ["lists","rows"],
736            [{"foo"=>"bar"}, {"q" => "ok"}]).
737            should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
738          @qs.rrun(["list_end"])
739          @qs.jsgets.should == ["end", ["tail"]]
740        end
741      end
742
743      describe "should buffer multiple chunks sent for a single row." do
744        it "should should buffer em" do
745          @qs.ddoc_run(@ddoc, ["lists","buffer-chunks"],
746            [{"foo"=>"bar"}, {"q" => "ok"}]).
747            should == ["start", ["bacon"], {"headers"=>{}}]
748          @qs.rrun(["list_row", {"key"=>"baz"}])
749          @qs.get_chunks.should == ["baz", "eggs"]
750          @qs.rrun(["list_row", {"key"=>"bam"}])
751          @qs.get_chunks.should == ["bam", "eggs"]
752          @qs.rrun(["list_end"])
753          @qs.jsgets.should == ["end", ["tail"]]
754        end
755      end
756      it "should end after 2" do
757        @qs.ddoc_run(@ddoc, ["lists","chunky"],
758          [{"foo"=>"bar"}, {"q" => "ok"}]).
759          should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
760
761        @qs.run(["list_row", {"key"=>"baz"}]).
762          should ==  ["chunks", ["baz"]]
763
764        @qs.run(["list_row", {"key"=>"bam"}]).
765          should ==  ["chunks", ["bam"]]
766
767        @qs.run(["list_row", {"key"=>"foom"}]).
768          should == ["end", ["foom", "early tail"]]
769        # here's where js has to discard quit properly
770        @qs.run(["reset"]).
771          should == true
772      end
773    end
774
775  describe "ddoc rewrites" do
776    describe "simple rewrite" do
777      before(:all) do
778        @ddoc = {
779          "_id" => "foo",
780          "rewrites" => functions["rewrite-basic"][LANGUAGE]
781        }
782        @qs.teach_ddoc(@ddoc)
783      end
784      it "should run normal" do
785        ok, resp = @qs.ddoc_run(@ddoc,
786          ["rewrites"],
787          [{"path" => "foo/bar"}, {"method" => "POST"}]
788        )
789        ok.should == "ok"
790        resp["path"].should == "new/location"
791      end
792    end
793
794    describe "no rule" do
795      before(:all) do
796        @ddoc = {
797          "_id" => "foo",
798          "rewrites" => functions["rewrite-no-rule"][LANGUAGE]
799        }
800        @qs.teach_ddoc(@ddoc)
801      end
802      it "should run normal" do
803        resp = @qs.ddoc_run(@ddoc,
804          ["rewrites"],
805          [{"path" => "foo/bar"}, {"method" => "POST"}]
806        )
807        resp.should == ['no_dispatch_rule']
808      end
809    end
810  end
811end
812
813
814
815def should_have_exited qs
816  begin
817    qs.run(["reset"])
818    "raise before this (except Erlang)".should == true
819  rescue RuntimeError => e
820    e.message.should == "no response"
821  rescue Errno::EPIPE
822    true.should == true
823  end
824end
825
826describe "query server that exits" do
827  before(:each) do
828    @qs = QueryServerRunner.run
829    @ddoc = {
830      "_id" => "foo",
831      "lists" => {
832        "capped" => functions["list-capped"][LANGUAGE],
833        "raw" => functions["list-raw"][LANGUAGE]
834      },
835      "shows" => {
836        "fatal" => functions["fatal"][LANGUAGE]
837      }
838    }
839    @qs.teach_ddoc(@ddoc)
840  end
841  after(:each) do
842    @qs.close
843  end
844
845  describe "only goes to 2 list" do
846    it "should exit if erlang sends too many rows" do
847      @qs.ddoc_run(@ddoc, ["lists","capped"],
848        [{"foo"=>"bar"}, {"q" => "ok"}]).
849        should == ["start", ["bacon"], {"headers"=>{}}]
850      @qs.run(["list_row", {"key"=>"baz"}]).should ==  ["chunks", ["baz"]]
851      @qs.run(["list_row", {"key"=>"foom"}]).should == ["chunks", ["foom"]]
852      @qs.run(["list_row", {"key"=>"fooz"}]).should == ["end", ["fooz", "early"]]
853      e = @qs.run(["list_row", {"key"=>"foox"}])
854      e[0].should == "error"
855      e[1].should == "unknown_command"
856      should_have_exited @qs
857    end
858  end
859
860  describe "raw list" do
861    it "should exit if it gets a non-row in the middle" do
862      @qs.ddoc_run(@ddoc, ["lists","raw"],
863        [{"foo"=>"bar"}, {"q" => "ok"}]).
864        should == ["start", ["first chunk", "ok"], {"headers"=>{}}]
865      e = @qs.run(["reset"])
866      e[0].should == "error"
867      e[1].should == "list_error"
868      should_have_exited @qs
869    end
870  end
871
872  describe "fatal error" do
873    it "should exit" do
874      @qs.ddoc_run(@ddoc, ["shows","fatal"],
875        [{"foo"=>"bar"}, {"q" => "ok"}]).
876        should == ["error", "error_key", "testing"]
877      should_have_exited @qs
878    end
879  end
880end
881
882describe "thank you for using the tests" do
883  it "for more info run with QS_TRACE=true or see query_server_spec.rb file header" do
884  end
885end
886