[Groonga-commit] ranguba/groonga-client at 56a06ab [master] Import request interface from groonga-client-rails

Back to archive index

Kouhei Sutou null+****@clear*****
Fri Dec 9 16:27:28 JST 2016


Kouhei Sutou	2016-12-09 16:27:28 +0900 (Fri, 09 Dec 2016)

  New Revision: 56a06ab966e106cff89ecfa344841edf4ef0edb1
  https://github.com/ranguba/groonga-client/commit/56a06ab966e106cff89ecfa344841edf4ef0edb1

  Message:
    Import request interface from groonga-client-rails

  Added files:
    lib/groonga/client/request.rb
    lib/groonga/client/request/base.rb
    lib/groonga/client/request/select.rb
    test/request/select/test-filter-parameter.rb
    test/request/select/test-match-columns-parameter.rb
    test/request/select/test-output-columns-parameter.rb
    test/request/select/test-sort-keys-columns-parameter.rb
    test/request/test-select.rb
  Modified files:
    lib/groonga/client.rb
    lib/groonga/client/response/select.rb
    test/run-test.rb

  Modified: lib/groonga/client.rb (+1 -1)
===================================================================
--- lib/groonga/client.rb    2016-12-09 16:08:24 +0900 (7e19fe5)
+++ lib/groonga/client.rb    2016-12-09 16:27:28 +0900 (46a33e5)
@@ -23,7 +23,7 @@ require "groonga/client/command"
 require "groonga/client/empty-request"
 require "groonga/client/protocol/gqtp"
 require "groonga/client/protocol/http"
-require "groonga/client/script-syntax"
+require "groonga/client/request"
 
 module Groonga
   class Client

  Added: lib/groonga/client/request.rb (+19 -0) 100644
===================================================================
--- /dev/null
+++ lib/groonga/client/request.rb    2016-12-09 16:27:28 +0900 (ca99c9f)
@@ -0,0 +1,19 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+require "groonga/client/script-syntax"
+
+require "groonga/client/request/select"

  Added: lib/groonga/client/request/base.rb (+95 -0) 100644
===================================================================
--- /dev/null
+++ lib/groonga/client/request/base.rb    2016-12-09 16:27:28 +0900 (4bf8688)
@@ -0,0 +1,95 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+module Groonga
+  class Client
+    module Request
+      class Base
+        def initialize(command_name, parameters=nil)
+          @command_name = command_name
+          @parameters = parameters
+        end
+
+        def response
+          @reponse ||= create_response
+        end
+
+        def parameter(name, value)
+          add_parameter(OverwriteMerger,
+                        RequestParameter.new(name, value))
+        end
+
+        def to_parameters
+          if****@param*****?
+            {}
+          else
+            @parameters.to_parameters
+          end
+        end
+
+        private
+        def add_parameter(merger_class, parameter)
+          merger = merger_class.new(@parameters, parameter)
+          create_request(merger)
+        end
+
+        def create_request(parameters)
+          self.class.new(@command_name, parameters)
+        end
+
+        def create_response
+          open_client do |client|
+            response = client.execute(@command_name, to_parameters)
+            raise ErrorResponse.new(response) unless response.success?
+            response
+          end
+        end
+
+        def open_client
+          Client.open do |client|
+            yield(client)
+          end
+        end
+      end
+
+      class RequestParameter
+        def initialize(name, value)
+          @name = name
+          @value = value
+        end
+
+        def to_parameters
+          {
+            @name => @value,
+          }
+        end
+      end
+
+      class ParameterMerger
+        def initialize(parameters1, parameters2)
+          @parameters1 = parameters1
+          @parameters2 = parameters2
+        end
+      end
+
+      class OverwriteMerger < ParameterMerger
+        def to_parameters
+          @parameters1.to_parameters.merge(@parameters2.to_parameters)
+        end
+      end
+    end
+  end
+end

  Added: lib/groonga/client/request/select.rb (+289 -0) 100644
===================================================================
--- /dev/null
+++ lib/groonga/client/request/select.rb    2016-12-09 16:27:28 +0900 (e0404e9)
@@ -0,0 +1,289 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+require "groonga/client/request/base"
+
+module Groonga
+  class Client
+    module Request
+      class Select < Base
+        def initialize(table_or_parameters)
+          if table_or_parameters.respond_to?(:to_parameters)
+            parameters = table_or_parameters
+          else
+            table_name = table_or_parameters
+            parameters = RequestParameter.new(:table, table_name)
+          end
+          super("select", parameters)
+        end
+
+        def match_columns(value)
+          add_parameter(OverwriteMerger,
+                        MatchColumnsParameter.new(value))
+        end
+
+        def query(value)
+          add_parameter(QueryMerger,
+                        RequestParameter.new(:query, value))
+        end
+
+        def filter(expression, values=nil)
+          add_parameter(FilterMerger,
+                        FilterParameter.new(expression, values))
+        end
+
+        def output_columns(value)
+          add_parameter(OverwriteMerger,
+                        OutputColumnsParameter.new(value))
+        end
+
+        def sort_keys(value)
+          add_parameter(OverwriteMerger,
+                        SortKeysParameter.new(value))
+        end
+        alias_method :sortby, :sort_keys
+        alias_method :sort, :sort_keys
+
+        def offset(value)
+          parameter(:offset, value)
+        end
+
+        def limit(value)
+          parameter(:limit, value)
+        end
+
+        def paginate(page, per_page: 10)
+          page ||= 1
+          page = page.to_i
+          if page <= 0
+            offset = 0
+          else
+            offset = per_page * (page - 1)
+          end
+          offset(offset).limit(per_page)
+        end
+
+        private
+        def create_request(parameters)
+          self.class.new(parameters)
+        end
+
+        def create_response
+          response = super
+          if paginated? and defined?(Kaminari)
+            response.extend(Kaminari::ConfigurationMethods::ClassMethods)
+            response.extend(Kaminari::PageScopeMethods)
+          end
+          response
+        end
+
+        def paginated?
+          parameters = to_parameters
+          parameters.key?(:offset) and parameters.key?(:limit)
+        end
+
+        # @private
+        class QueryMerger < ParameterMerger
+          def to_parameters
+            params1 =****@param*****_parameters
+            params2 =****@param*****_parameters
+            params = params1.merge(params2)
+            query1 = params1[:query]
+            query2 = params2[:query]
+            if query1.present? and query2.present?
+              params[:query] = "(#{query1}) (#{query2})"
+            else
+              params[:query] = (query1 || query2)
+            end
+            params
+          end
+        end
+
+        # @private
+        class FilterMerger < ParameterMerger
+          def to_parameters
+            params1 =****@param*****_parameters
+            params2 =****@param*****_parameters
+            params = params1.merge(params2)
+            filter1 = params1[:filter]
+            filter2 = params2[:filter]
+            if filter1.present? and filter2.present?
+              params[:filter] = "(#{filter1}) && (#{filter2})"
+            else
+              params[:filter] = (filter1 || filter2)
+            end
+            params
+          end
+        end
+
+        # @private
+        class MatchColumnsParameter
+          def initialize(match_columns)
+            @match_columns = match_columns
+          end
+
+          def to_parameters
+            case @match_columns
+            when ::Array
+              return {} if @match_columns.empty?
+              match_columns = @match_columns.join(", ")
+            when Symbol
+              match_columns = @match_columns.to_s
+            when String
+              return {} if /\A\s*\z/ === @match_columns
+              match_columns = @match_columns
+            when NilClass
+              return {}
+            else
+              match_columns = @match_columns
+            end
+            {
+              match_columns: match_columns,
+            }
+          end
+        end
+
+        # @private
+        class FilterParameter
+          def initialize(expression, values)
+            @expression = expression
+            @values = values
+          end
+
+          def to_parameters
+            case @expression
+            when String
+              return {} if /\A\s*\z/ === @expression
+              expression = @expression
+            when NilClass
+              return {}
+            else
+              expression = @expression
+            end
+
+            if****@value*****_a?(::Hash) and not****@value*****?
+              escaped_values = {}
+              @values.each do |key, value|
+                escaped_values[key] = escape_filter_value(value)
+              end
+              expression = expression % escaped_values
+            end
+
+            {
+              filter: expression,
+            }
+          end
+
+          private
+          def escape_filter_value(value)
+            case value
+            when Numeric
+              value
+            when TrueClass, FalseClass
+              value
+            when NilClass
+              "null"
+            when String
+              ScriptSyntax.format_string(value)
+            when Symbol
+              ScriptSyntax.format_string(value.to_s)
+            when ::Array
+              escaped_value = "["
+              value.each_with_index do |element, i|
+                escaped_value << ", " if i > 0
+                escaped_value << escape_filter_value(element)
+              end
+              escaped_value << "]"
+              escaped_value
+            when ::Hash
+              escaped_value = "{"
+              value.each_with_index do |(k, v), i|
+                escaped_value << ", " if i > 0
+                escaped_value << escape_filter_value(k.to_s)
+                escaped_value << ": "
+                escaped_value << escape_filter_value(v)
+              end
+              escaped_value << "}"
+              escaped_value
+            else
+              value
+            end
+          end
+        end
+
+        # @private
+        class OutputColumnsParameter
+          def initialize(output_columns)
+            @output_columns = output_columns
+          end
+
+          def to_parameters
+            case @output_columns
+            when ::Array
+              return {} if @output_columns.empty?
+              output_columns = @output_columns.join(", ")
+            when Symbol
+              output_columns = @output_columns.to_s
+            when String
+              return {} if /\A\s*\z/ === @output_columns
+              output_columns = @output_columns
+            when NilClass
+              return {}
+            else
+              output_columns = @output_columns
+            end
+
+            parameters = {
+              output_columns: output_columns,
+            }
+            if output_columns.include?("(")
+              parameters[:command_version] = "2"
+            end
+            parameters
+          end
+        end
+
+        # @private
+        class SortKeysParameter
+          def initialize(keys)
+            @keys = keys
+          end
+
+          def to_parameters
+            case @keys
+            when ::Array
+              return {} if****@keys*****?
+              keys =****@keys*****(&:to_s).join(", ")
+            when Symbol
+              keys =****@keys*****_s
+            when String
+              return {} if /\A\s*\z/ === @keys
+              keys = @keys
+            when NilClass
+              return {}
+            else
+              keys = @keys
+            end
+            {
+              sort_keys: keys,
+              sortby: keys, # For backward compatibility
+            }
+          end
+        end
+      end
+    end
+  end
+end

  Modified: lib/groonga/client/response/select.rb (+17 -1)
===================================================================
--- lib/groonga/client/response/select.rb    2016-12-09 16:08:24 +0900 (b7c0af8)
+++ lib/groonga/client/response/select.rb    2016-12-09 16:27:28 +0900 (54a2fde)
@@ -26,6 +26,8 @@ module Groonga
         # @return [Integer] The number of records that match againt
         #   a search condition.
         attr_accessor :n_hits
+        # For Kaminari
+        alias_method :total_count, :n_hits
         attr_accessor :records
 
         # @return [::Array<Groonga::Client::Response::Select::Drilldown>,
@@ -42,6 +44,16 @@ module Groonga
           parse_body(body)
         end
 
+        # For Kaminari
+        def limit_value
+          (@command[:limit] || 10).to_i
+        end
+
+        # For Kaminari
+        def offset_value
+          (@command[:offset] || 0).to_i
+        end
+
         private
         def parse_body(body)
           if body.is_a?(::Array)
@@ -74,7 +86,7 @@ module Groonga
           end
 
           (raw_records || []).collect do |raw_record|
-            record = {}
+            record = Record.new
             columns.each_with_index do |(name, type), i|
               record[name] = convert_value(raw_record[i], type)
             end
@@ -139,6 +151,10 @@ module Groonga
           drilldowns
         end
 
+        class Record < ::Hash
+          include Hashie::Extensions::MethodAccess
+        end
+
         class Drilldown < Struct.new(:key, :n_hits, :records)
           # @deprecated since 0.2.6. Use {#records} instead.
           alias_method :items, :records

  Added: test/request/select/test-filter-parameter.rb (+97 -0) 100644
===================================================================
--- /dev/null
+++ test/request/select/test-filter-parameter.rb    2016-12-09 16:27:28 +0900 (7e76631)
@@ -0,0 +1,97 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+class TestRequestSelectFilterParmater < Test::Unit::TestCase
+  def filter_parameter(expression, values=nil)
+    Groonga::Client::Request::Select::FilterParameter.new(expression, values)
+  end
+
+  def to_parameters(expression, values=nil)
+    filter_parameter(expression, values).to_parameters
+  end
+
+  sub_test_case("expression") do
+    def test_nil
+      assert_equal({},
+                   to_parameters(nil))
+    end
+
+    def test_string
+      assert_equal({
+                     :filter => "age <= 20",
+                   },
+                   to_parameters("age <= 20"))
+    end
+
+    def test_empty_string
+      assert_equal({},
+                   to_parameters(""))
+    end
+  end
+
+  sub_test_case("values") do
+    def test_string
+      filter = <<-'FILTER'.strip
+title == "[\"He\\ llo\"]"
+      FILTER
+      assert_equal({
+                     :filter => filter,
+                   },
+                   to_parameters("title == %{value}",
+                                 :value => "[\"He\\ llo\"]"))
+    end
+
+    def test_symbol
+      assert_equal({
+                     :filter => "title == \"Hello\"",
+                   },
+                   to_parameters("title == %{value}",
+                                 :value => :Hello))
+    end
+
+    def test_number
+      assert_equal({
+                     :filter => "age <= 29",
+                   },
+                   to_parameters("age <= %{value}",
+                                 :value => 29))
+    end
+
+    def test_true
+      assert_equal({
+                     :filter => "published == true",
+                   },
+                   to_parameters("published == %{value}",
+                                 :value => true))
+    end
+
+    def test_false
+      assert_equal({
+                     :filter => "published == false",
+                   },
+                   to_parameters("published == %{value}",
+                                 :value => false))
+    end
+
+    def test_nil
+      assert_equal({
+                     :filter => "function(null)",
+                   },
+                   to_parameters("function(%{value})",
+                                 :value => nil))
+    end
+  end
+end

  Added: test/request/select/test-match-columns-parameter.rb (+57 -0) 100644
===================================================================
--- /dev/null
+++ test/request/select/test-match-columns-parameter.rb    2016-12-09 16:27:28 +0900 (6aa7c3e)
@@ -0,0 +1,57 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+class TestRequestSelectMatchColumnsParmater < Test::Unit::TestCase
+  def match_columns_parameter(match_columns)
+    Groonga::Client::Request::Select::MatchColumnsParameter.new(match_columns)
+  end
+
+  def test_nil
+    assert_equal({},
+                 match_columns_parameter(nil).to_parameters)
+  end
+
+  def test_string
+    assert_equal({
+                   :match_columns => "title",
+                 },
+                 match_columns_parameter("title").to_parameters)
+  end
+
+  def test_empty_string
+    assert_equal({},
+                 match_columns_parameter("").to_parameters)
+  end
+
+  def test_symbol
+    assert_equal({
+                   :match_columns => "title",
+                 },
+                 match_columns_parameter(:title).to_parameters)
+  end
+
+  def test_array
+    assert_equal({
+                   :match_columns => "title, body",
+                 },
+                 match_columns_parameter(["title", "body"]).to_parameters)
+  end
+
+  def test_empty_array
+    assert_equal({},
+                 match_columns_parameter([]).to_parameters)
+  end
+end

  Added: test/request/select/test-output-columns-parameter.rb (+66 -0) 100644
===================================================================
--- /dev/null
+++ test/request/select/test-output-columns-parameter.rb    2016-12-09 16:27:28 +0900 (cdf5db8)
@@ -0,0 +1,66 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+class TestRequestSelectOutputColumnsParmater < Test::Unit::TestCase
+  def output_columns_parameter(output_columns)
+    Groonga::Client::Request::Select::OutputColumnsParameter.new(output_columns)
+  end
+
+  def test_nil
+    assert_equal({},
+                 output_columns_parameter(nil).to_parameters)
+  end
+
+  def test_string
+    assert_equal({
+                   :output_columns => "title",
+                 },
+                 output_columns_parameter("title").to_parameters)
+  end
+
+  def test_empty_string
+    assert_equal({},
+                 output_columns_parameter("").to_parameters)
+  end
+
+  def test_symbol
+    assert_equal({
+                   :output_columns => "title",
+                 },
+                 output_columns_parameter(:title).to_parameters)
+  end
+
+  def test_array
+    assert_equal({
+                   :output_columns => "title, body",
+                 },
+                 output_columns_parameter(["title", "body"]).to_parameters)
+  end
+
+  def test_empty_array
+    assert_equal({},
+                 output_columns_parameter([]).to_parameters)
+  end
+
+  def test_function
+    parameter = output_columns_parameter(["title", "snippet_html(body)"])
+    assert_equal({
+                   :output_columns => "title, snippet_html(body)",
+                   :command_version => "2",
+                 },
+                 parameter.to_parameters)
+  end
+end

  Added: test/request/select/test-sort-keys-columns-parameter.rb (+64 -0) 100644
===================================================================
--- /dev/null
+++ test/request/select/test-sort-keys-columns-parameter.rb    2016-12-09 16:27:28 +0900 (5810e11)
@@ -0,0 +1,64 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+class TestRequestSelectSortKeysParmater < Test::Unit::TestCase
+  def sort_keys_parameter(sort_keys)
+    Groonga::Client::Request::Select::SortKeysParameter.new(sort_keys)
+  end
+
+  def to_parameters(sort_keys)
+    sort_keys_parameter(sort_keys).to_parameters
+  end
+
+  def test_nil
+    assert_equal({},
+                 to_parameters(nil))
+  end
+
+  def test_string
+    assert_equal({
+                   :sort_keys => "-_score, _id",
+                   :sortby    => "-_score, _id",
+                 },
+                 to_parameters("-_score, _id"))
+  end
+
+  def test_empty_string
+    assert_equal({},
+                 to_parameters(""))
+  end
+
+  def test_symbol
+    assert_equal({
+                   :sort_keys => "_score",
+                   :sortby    => "_score",
+                 },
+                 to_parameters(:_score))
+  end
+
+  def test_array
+    assert_equal({
+                   :sort_keys => "-_score, _id",
+                   :sortby    => "-_score, _id",
+                 },
+                 to_parameters(["-_score", :_id]))
+  end
+
+  def test_empty_array
+    assert_equal({},
+                 to_parameters([]))
+  end
+end

  Added: test/request/test-select.rb (+494 -0) 100644
===================================================================
--- /dev/null
+++ test/request/test-select.rb    2016-12-09 16:27:28 +0900 (1448dd7)
@@ -0,0 +1,494 @@
+# Copyright (C) 2016  Kouhei Sutou <kou �� clear-code.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+class TestSelectResponse < Test::Unit::TestCase
+  module ClientFixture
+    class << self
+      def included(base)
+        super
+        base.class_eval do
+          setup :setup_client
+          teardown :teardown_client
+        end
+      end
+    end
+
+    def setup_client
+      @client = nil
+    end
+
+    def teardown_client
+      @client.close if @client
+    end
+  end
+
+  module Utils
+    def client
+      @client ||= open_client
+    end
+
+    def open_options
+      url = "#{@protocol}://"
+      if @user and @password
+        url << URI.encode_www_form_component(@user)
+        url << ":"
+        url << URI.encode_www_form_component(@password)
+        url << "@"
+      end
+      url << "#{@address}:#{@port}"
+      {:url => url}
+    end
+
+    def open_client(&block)
+      Groonga::Client.open(open_options, &block)
+    end
+
+    def stub_response(body, output_type=:json)
+      @response_body = body
+      @response_output_type = output_type
+    end
+  end
+
+  module Assertions
+    NORMALIZED_START_TIME   = Time.parse("2013-05-23T16:43:39+09:00").to_i
+    NORMALIZED_ELAPSED_TIME = 29
+    def normalize_header(header)
+      normalized_header = header.dup
+      start_time = header[1]
+      if start_time.is_a?(Numeric)
+        normalized_header[1] = NORMALIZED_START_TIME
+      end
+      elapsed_time = header[2]
+      if elapsed_time.is_a?(Numeric)
+        normalized_header[2] = NORMALIZED_ELAPSED_TIME
+      end
+      normalized_header
+    end
+
+    def assert_header(response)
+      normalized_header = normalize_header(response.header)
+      assert_equal([0, NORMALIZED_START_TIME, NORMALIZED_ELAPSED_TIME],
+                   normalized_header)
+    end
+
+    def assert_response(expected_body, response)
+      if @response_output_type == :none
+        expected_header = nil
+        actual_header = response.header
+      else
+        expected_header = [
+          0,
+          NORMALIZED_START_TIME,
+          NORMALIZED_ELAPSED_TIME,
+        ]
+        actual_header = normalize_header(response.header)
+      end
+      actual_body = response.body
+      actual_body = yield(actual_body) if block_given?
+      assert_equal({
+                     :header => expected_header,
+                     :body   => expected_body,
+                   },
+                   {
+                     :header => actual_header,
+                     :body   => actual_body,
+                   })
+    end
+  end
+
+  module OutputTypeTests
+    def test_dump
+      dumped_commands = "table_create TEST_TABLE TABLE_NO_KEY"
+      stub_response(dumped_commands, :none)
+      response = client.dump
+      assert_response(dumped_commands, response)
+    end
+
+    def test_xml
+      stub_response(<<-XML, :xml)
+<TABLE_LIST>
+<HEADER>
+<PROPERTY>
+<TEXT>id</TEXT>
+<TEXT>UInt32</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>name</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>path</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>flags</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>domain</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>range</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>default_tokenizer</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY>
+<PROPERTY>
+<TEXT>normalizer</TEXT>
+<TEXT>ShortText</TEXT></PROPERTY></HEADER>
+<TABLE>
+<INT>256</INT>
+<TEXT>Users</TEXT>
+<TEXT>/tmp/db/db.0000100</TEXT>
+<TEXT>TABLE_HASH_KEY|PERSISTENT</TEXT>
+<NULL/>
+<NULL/>
+<NULL/>
+<NULL/></TABLE></TABLE_LIST>
+      XML
+      response = client.table_list(:output_type => :xml)
+      expected_body = [
+        [
+          ["id", "UInt32"],
+          ["name", "ShortText"],
+          ["path", "ShortText"],
+          ["flags", "ShortText"],
+          ["domain", "ShortText"],
+          ["range", "ShortText"],
+          ["default_tokenizer", "ShortText"],
+          ["normalizer", "ShortText"],
+        ],
+        [
+          256,
+          "Users",
+          "/tmp/db/db.0000100",
+          "TABLE_HASH_KEY|PERSISTENT",
+          nil,
+          nil,
+          nil,
+          nil,
+        ],
+      ]
+      assert_response(expected_body, response)
+    end
+  end
+
+  module ColumnsTests
+    def test_not_exist
+      stub_response('{"key":"value"}')
+      response = client.status
+      assert_response({"key" => "value"}, response)
+    end
+
+    def disabled_test_exist
+      stub_response(<<-JSON)
+[[["name","ShortText"],
+["age","UInt32"]],
+["Alice",32],
+["Bob",21]]
+JSON
+      expected_table_infos = [
+        {:name => "Alice", :age => 32},
+        {:name => "Bob", :age => 21}
+      ]
+      response = client.table_list
+      assert_response(expected_table_infos, response) do |actual_body|
+        actual_body.collect do |value|
+          value.table_info
+        end
+      end
+    end
+  end
+
+  module ParametersTests
+    def test_integer
+      stub_response("100")
+      response = client.cache_limit(:max => 4)
+      assert_response(100, response)
+    end
+  end
+
+  module OpenTests
+    def test_return_value
+      stub_response("['not-used']")
+      response = open_client do |client|
+        "response"
+      end
+      assert_equal("response", response)
+    end
+
+    def test_open_components
+      stub_response("[29]")
+      options = {
+        :host => @address,
+        :port => @port,
+        :protocol => @protocol,
+      }
+      response = Groonga::Client.open(options) do |client|
+        client.status
+      end
+      assert_equal([29], response.body)
+    end
+  end
+
+  module LoadTests
+    def test_load_json
+      values = [
+        {"content" => "1st content"},
+        {"content" => "2nd content"},
+        {"content" => "3rd content"},
+      ]
+      stub_response("[#{values.size}]")
+      response = client.load(:table => "Memos",
+                             :values => JSON.generate(values))
+      assert_equal([values.size], response.body)
+      assert_equal([values],
+                   @actual_commands.collect(&:values))
+    end
+
+    def test_load_array
+      values = [
+        {"content" => "1st content"},
+        {"content" => "2nd content"},
+        {"content" => "3rd content"},
+      ]
+      stub_response("[#{values.size}]")
+      response = client.load(:table => "Memos",
+                             :values => values)
+      assert_equal([values.size], response.body)
+      assert_equal([values],
+                   @actual_commands.collect(&:values))
+    end
+  end
+
+  module DefaultOptionsTests
+    def test_default_options
+      change_default_options(open_options) do
+        expected_response = {"key" => "value"}
+        stub_response(expected_response.to_json)
+        response = Groonga::Client.open do |client|
+          client.status
+        end
+        assert_equal(expected_response, response.body)
+      end
+    end
+
+    def change_default_options(options)
+      default_options = Groonga::Client.default_options
+      begin
+        Groonga::Client.default_options = options
+        yield
+      ensure
+        Groonga::Client.default_options = default_options
+      end
+    end
+  end
+
+  module Tests
+    include Utils
+    include Assertions
+
+    include OutputTypeTests
+    include ColumnsTests
+    include ParametersTests
+    include OpenTests
+    include LoadTests
+    include DefaultOptionsTests
+  end
+
+  class TestGQTP < self
+    include Tests
+    include ClientFixture
+
+    def setup
+      @address = "127.0.0.1"
+      @server = TCPServer.new(@address, 0)
+      @port =****@serve*****[1]
+      @protocol = :gqtp
+
+      @user = nil
+      @password = nil
+      @actual_commands = []
+      @response_body = nil
+      @thread = Thread.new do
+        client =****@serve*****
+        @server.close
+
+        loop do
+          raw_header = client.read(GQTP::Header.size)
+          break if raw_header.nil?
+
+          header = GQTP::Header.parse(raw_header)
+          body = client.read(header.size)
+          @actual_commands << Groonga::Command::Parser.parse(body)
+
+          response_header = GQTP::Header.new
+          response_header.size = @response_body.bytesize
+
+          client.write(response_header.pack)
+          client.write(@response_body)
+        end
+
+        client.close
+      end
+    end
+
+    def teardown
+      @thread.kill
+    end
+  end
+
+  class TestHTTP < self
+    include Tests
+    include ClientFixture
+
+    def setup
+      @address = "127.0.0.1"
+      @server = TCPServer.new(@address, 0)
+      @port =****@serve*****[1]
+      @protocol = :http
+
+      setup_authentication
+      @request_headers = {}
+      @request_path = nil
+      @actual_commands = []
+      @response_body = nil
+      @thread = Thread.new do
+        client =****@serve*****
+        first_line = client.gets
+        if /\A([\w]+) ([^ ]+) HTTP/ =~ first_line
+          # http_method = $1
+          @request_path = $2
+          headers = {}
+          client.each_line do |line|
+            case line
+            when "\r\n"
+              break
+            else
+              name, value = line.strip.split(/: */, 2)
+              headers[name.downcase] = value
+            end
+          end
+          @request_headers = headers
+          content_length = headers["content-length"]
+          if content_length
+            body = client.read(Integer(content_length))
+          else
+            body = nil
+          end
+          command = Groonga::Command::Parser.parse(@request_path)
+          command[:values] = body if body
+          @actual_commands << command
+        end
+        @server.close
+
+        status = 0
+        start = Time.now.to_f
+        elapsed = rand
+        case @response_output_type
+        when :json
+          header = "[#{status},#{start},#{elapsed}]"
+          body = "[#{header},#{@response_body}]"
+        when :xml
+          body = <<-XML
+<RESULT CODE="#{status}" UP="#{start}" ELAPSED="#{elapsed}">
+#{@response_body}
+</RESULT>
+          XML
+        else
+          body = @response_body
+        end
+        header = <<-EOH
+HTTP/1.1 200 OK
+Connection: close
+Content-Type: application/json
+Content-Length: #{body.bytesize}
+
+EOH
+        client.write(header)
+        client.write(body)
+        client.close
+      end
+    end
+
+    def setup_authentication
+      @user = nil
+      @password = nil
+    end
+
+    class TestLoad < self
+      def test_path
+        stub_response("[]")
+        client.load(:table => "Memos", :values => [])
+        assert_equal("/d/load?table=Memos", @request_path)
+      end
+    end
+
+    class TestBasicAuthentication < self
+      def setup_authentication
+        @user = "Aladdin"
+        @password = "open sesame"
+      end
+
+      def test_request_header
+        stub_response("[]")
+        client.status
+        assert_equal("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
+                     @request_headers["authorization"])
+      end
+
+      def test_open_components
+        stub_response("[]")
+        options = {
+          :host => @address,
+          :port => @port,
+          :protocol => @protocol,
+          :user => @user,
+          :password => @password,
+        }
+        Groonga::Client.open(options) do |client|
+          client.status
+        end
+        assert_equal("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
+                     @request_headers["authorization"])
+      end
+    end
+
+    class TestPath < self
+      def component_based_open_options
+        {
+          :protocol => @protocol,
+          :host => @address,
+          :port => @port,
+        }
+      end
+
+      def test_with_trailing_slash
+        stub_response("[]")
+        options = component_based_open_options.merge(:path => "/sub_path/")
+        Groonga::Client.open(options) do |client|
+          client.status
+        end
+        assert_equal("/sub_path/d/status", @request_path)
+      end
+
+      def test_without_trailing_slash
+        stub_response("[]")
+        options = component_based_open_options.merge(:path => "/sub_path")
+        Groonga::Client.open(options) do |client|
+          client.status
+        end
+        assert_equal("/sub_path/d/status", @request_path)
+      end
+    end
+  end
+end

  Modified: test/run-test.rb (+1 -0)
===================================================================
--- test/run-test.rb    2016-12-09 16:08:24 +0900 (7dbc9fd)
+++ test/run-test.rb    2016-12-09 16:27:28 +0900 (4c6dbf3)
@@ -41,6 +41,7 @@ test_dir = base_dir + "test"
 $LOAD_PATH.unshift(lib_dir.to_s)
 
 require "test-unit"
+require "groonga/client"
 
 Thread.abort_on_exception = true
 
-------------- next part --------------
HTML����������������������������...
Descargar 



More information about the Groonga-commit mailing list
Back to archive index