specから理解するinfluxdb-ruby gemの機能 1

influxdb-rubyとは
InfluxDBの開発元のinfluxdataが出しているclient libraryです。
そのテストコードを通して、influxdbを少し理解していこうという試みです。


## client_spec.rb
InfluxDBへの接続の基本的なところ

```ruby:client_spec.rb
...
  context "with basic auth" do
    let(:args) { { auth_method: 'basic_auth' } }

    let(:credentials) { "username:password" }
    let(:auth_header) { { "Authorization" => "Basic " + Base64.encode64(credentials).chomp } }

    let(:stub_url)  { "http://influxdb.test:9999/" }
    let(:url)       { subject.send(:full_url, '/') }

    it "GET" do
      stub_request(:get, stub_url).with(headers: auth_header).to_return(body: '[]')
      expect(subject.get(url, parse: true)).to eq []
    end

    it "POST" do
      stub_request(:post, stub_url).with(headers: auth_header).to_return(status: 204)
      expect(subject.post(url, {})).to be_a(Net::HTTPNoContent)
    end
  end
...
```
stubに対して、GETとPOSTをそれぞれテスト

```ruby:client_spec.rb
...
  describe "#full_url" do
    it "returns String" do
      expect(subject.send(:full_url, "/unknown")).to be_a String
    end

    it "escapes params" do
      url = subject.send(:full_url, "/unknown", value: ' !@#$%^&*()/\\_+-=?|`~')
      expect(url).to include("value=+%21%40%23%24%25%5E%26%2A%28%29%2F%5C_%2B-%3D%3F%7C%60%7E")
    end

    context "with prefix" do
      let(:args) { { prefix: '/dev' } }

      it "returns path with prefix" do
        expect(subject.send(:full_url, "/series")).to start_with("/dev")
      end
    end
  end
...
```
クエリ文字列を作るfull_urlの動作を確認
ちなみにfull_urlは
lib/influxdb/query/core.rbにあります。

```ruby:lib/influxdb/query/core.rb
...
      def full_url(path, params = {})
        if config.auth_method == "params".freeze
          params[:u] = config.username
          params[:p] = config.password
        end

        query = params.map do |k, v|
          [CGI.escape(k.to_s), "=".freeze, CGI.escape(v.to_s)].join
        end.join("&".freeze)

        URI::Generic.build(path: File.join(config.prefix, path), query: query).to_s
      end
...
```

## config_spec.rb
接続情報などを設定できるかの確認

```ruby:config_spec.rb
  context "with both a database and options specified" do
    let(:args) do
      [
        "database",
        host: "host",
        port: "port",
        username: "username",
        password: "password",
        time_precision: "m"
      ]
    end

    specify { expect(conf.database).to eq "database" }
    specify { expect(conf.hosts).to eq ["host"] }
    specify { expect(conf.port).to eq "port" }
    specify { expect(conf.username).to eq "username" }
    specify { expect(conf.password).to eq "password" }
    specify { expect(conf.time_precision).to eq "m" }
    specify { expect(conf.epoch).to be_falsey }
  end
```
データベース名、ホスト、ユーザ名、パスワードなどの基本的な情報

## logging_spec.rb
ログまわり

```ruby:logging_spec.rb
...
  class LoggerTest # :nodoc:
    include InfluxDB::Logging

    def write_to_log(level, message)
      log(level, message)
    end
  end
...
  context "when included in classes" do
    subject { LoggerTest.new }

    it "logs" do
      expect(InfluxDB::Logging.logger).to receive(:debug).with(an_instance_of(String)).once
      subject.write_to_log(:debug, 'test')
    end
  end
...
```

## max_queue_spec.rb
キューの最大設定値の確認

```ruby:max_queue_spec.rb
...
  context "#new" do
    it "allows max_depth to be set" do
      expect(described_class.new(500).max).to eq 500
    end
  end
...
  context "#push" do
    let(:queue) { described_class.new(5) }

    it "allows an item to be added if the queue is not full" do
      expect(queue.size).to be_zero
      queue.push(1)
      expect(queue.size).to eq 1
    end

    it "doesn't allow items to be added if the queue is full" do
      expect(queue.size).to be_zero
      5.times { |n| queue.push(n) }
      expect(queue.size).to eq 5
      queue.push(6)
      expect(queue.size).to eq 5
    end
  end
...
```
キューの最大値を設定でき、最大値を超えるキューは入れらないことや
入れたキューの数を確認

## point_value_spec.rb
書き込もうとしているデータをdumpメソッドの出力で確認

```ruby:point_value_spec.rb
...
    context "with all possible data passed" do
      let(:expected_value) do
        'responses,region=eu,status=200 value=5i,threshold=0.54 1436349652'
      end
      it 'should have proper form' do
        point = InfluxDB::PointValue.new(series: "responses",
                                         values: { value: 5, threshold: 0.54 },
                                         tags: { region: 'eu', status: 200 },
                                         timestamp: 1_436_349_652)

        expect(point.dump).to eq(expected_value)
      end
    end
...
    context "with no tags" do
      let(:expected_value) do
        "responses value=5i,threshold=0.54 1436349652"
      end
      it 'should have proper form' do
        point = InfluxDB::PointValue.new(series: "responses",
                                         values: { value: 5, threshold: 0.54 },
                                         timestamp: 1_436_349_652)

        expect(point.dump).to eq(expected_value)
      end
    end
...
    context "with values only" do
      let(:expected_value) do
        "responses value=5i,threshold=0.54"
      end
      it 'should have proper form' do
        point = InfluxDB::PointValue.new(series: "responses",
                                         values: { value: 5, threshold: 0.54 })

        expect(point.dump).to eq(expected_value)
      end
    end

    context "empty tag values" do
      let(:expected_value) do
        "responses,region=eu value=5i"
      end

      it "should be omitted" do
        point = InfluxDB::PointValue.new(series: "responses",
                                         values: { value: 5 },
                                         tags: { region: "eu", status: nil, other: "", nil => "ignored", "" => "ignored" })
        expect(point.dump).to eq(expected_value)
      end
    end
...
```
tagsとvaluesを設定。
timestampはoptional

## query_builder_spec.rb
クエリ作成の確認

```ruby:query_builder_spec.rb
...
  describe "#build" do
    subject { builder.build(query, params) }

    context "named parameters" do
      let(:query)  { "SELECT value FROM rpm WHERE f = %{f_val} group by time(%{minutes}m)" }
      let(:params) { { f_val: "value", minutes: 5 } }

      it { is_expected.to eq "SELECT value FROM rpm WHERE f = 'value' group by time(5m)" }

      context "with string keys" do
        let(:params) { { "f_val" => "value", "minutes" => 5 } }

        it { is_expected.to eq "SELECT value FROM rpm WHERE f = 'value' group by time(5m)" }
      end
    end

    context "positional parameter" do
      let(:query)  { "SELECT value FROM rpm WHERE time > %{1}" }
      let(:params) { [1_437_019_900] }

      it { is_expected.to eq "SELECT value FROM rpm WHERE time > 1437019900" }
    end

    context "missing parameters" do
      let(:query)  { "SELECT value FROM rpm WHERE time > %{1}" }
      let(:params) { [] }

      it { expect { subject }.to raise_error(/key.1. not found/) }
    end

    context "extra parameters" do
      let(:query)  { "SELECT value FROM rpm WHERE time > %{a}" }
      let(:params) { { "a" => 0, "b" => 2 } }

      it { is_expected.to eq "SELECT value FROM rpm WHERE time > 0" }
    end
  end
...
```
主にSQLの生成

## worker_spec.rb


```ruby:worker_spec.rb
...
  describe "#push" do
    let(:payload1) { "responses,region=eu value=5" }
    let(:payload2) { "responses,region=eu value=6" }
    let(:aggregate) { "#{payload1}\n#{payload2}" }

    it "writes aggregate payload to the client" do
      queue = Queue.new
      allow(fake_client).to receive(:write) do |data, _precision|
        queue.push(data)
      end
      worker.push(payload1)
      worker.push(payload2)

      Timeout.timeout(described_class::SLEEP_INTERVAL) do
        result = queue.pop
        expect(result).to eq aggregate
      end
    end
  end
...
```
非同期で書き込んだ２つキューの結果を
同時に受け取ることもできる確認(!?)


## 次回
spec/influxdb・cases下のテストを扱います。