require 'spec_helper' require 'td/client/spec_resources' describe 'Table API' do include_context 'spec symbols' include_context 'common helper' let :api do API.new(nil) end let :client do Client.new(apikey) end describe "'create_log_table' API" do it 'should return 404 error if the database does not exist' do err_msg = "Create log table failed: Couldn't find UserDatabase with name = #{db_name}" stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e(table_name)}/log"). to_return(:status => 404, :body => {'message' => err_msg}.to_json) expect { api.create_log_table(db_name, table_name) }.to raise_error(TreasureData::NotFoundError, /#{err_msg}/) end it 'should create a new table if the database exists' do stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e(table_name)}/log"). to_return(:body => {'database' => db_name, 'table' => table_name, 'type' => 'log'}.to_json) expect(api.create_log_table(db_name, table_name)).to be true end it 'should create a new table with params' do stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e(table_name)}/log"). with(:body => {'include_v' => 'false'}). to_return(:body => {'database' => db_name, 'table' => table_name, 'type' => 'log', 'include_v' => 'false'}.to_json) expect(api.create_log_table(db_name, table_name, include_v: false)).to be true end it 'should return 400 error with invalid name' do invalid_name = 'a' err_msg = "Name must be 3 to 256 characters, got #{invalid_name.length} characters. name = '#{invalid_name}'" stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e invalid_name}/log"). to_return(:status => 400, :body => {'message' => err_msg}.to_json) expect { api.create_log_table(db_name, invalid_name) }.to raise_error(TreasureData::APIError, /#{err_msg}/) end it 'should return 409 error with duplicated name' do err_msg = "Table #{table_name} already exists" stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e table_name}/log"). to_return(:status => 409, :body => {'message' => err_msg}.to_json) expect { api.create_log_table(db_name, table_name) }.to raise_error(TreasureData::AlreadyExistsError, /#{err_msg}/) end end describe "'create_log_table' client API" do it 'should return 404 error if the database does not exist' do err_msg = "Create log table failed: Couldn't find UserDatabase with name = #{db_name}" stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e(table_name)}/log"). to_return(:status => 404, :body => {'message' => err_msg}.to_json) expect { client.create_log_table(db_name, table_name) }.to raise_error(TreasureData::NotFoundError, /#{err_msg}/) end it 'should create a new table if the database exists' do stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e(table_name)}/log"). to_return(:body => {'database' => db_name, 'table' => table_name, 'type' => 'log'}.to_json) expect(client.create_log_table(db_name, table_name)).to be true end it 'should create a new table with params' do stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e(table_name)}/log"). with(:body => {'include_v' => 'false'}). to_return(:body => {'database' => db_name, 'table' => table_name, 'type' => 'log', 'include_v' => 'false'}.to_json) expect(client.create_log_table(db_name, table_name, include_v: false)).to be true end it 'should return 400 error with invalid name' do invalid_name = 'a' err_msg = "Name must be 3 to 256 characters, got #{invalid_name.length} characters. name = '#{invalid_name}'" stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e invalid_name}/log"). to_return(:status => 400, :body => {'message' => err_msg}.to_json) expect { client.create_log_table(db_name, invalid_name) }.to raise_error(TreasureData::APIError, /#{err_msg}/) end it 'should return 409 error with duplicated name' do err_msg = "Table #{table_name} already exists" stub_api_request(:post, "/v3/table/create/#{e db_name}/#{e table_name}/log"). to_return(:status => 409, :body => {'message' => err_msg}.to_json) expect { client.create_log_table(db_name, table_name) }.to raise_error(TreasureData::AlreadyExistsError, /#{err_msg}/) end end describe "'list_tables' API" do it 'should list the tables in a Hash whose values include type, count, created_at, updated_at, schema, ...' do tables = [ ["table_1", "item", "[[\"time\",\"long\"],[\"value\",\"string\"]]", 111, "2013-01-21 01:51:41 UTC", "2014-01-21 01:51:41 UTC"], ["table_2", "log", "[[\"time\",\"long\"],[\"value\",\"long\"]]", 222, "2013-02-22 02:52:42 UTC", "2014-02-22 02:52:42 UTC"], ["table_3", "item", "[[\"time\",\"long\"],[\"value\",\"string\"]]", 333, "2013-03-23 03:53:43 UTC", "2014-03-23 03:53:43 UTC"], ["table_4", "log", "[[\"time\",\"long\"],[\"value\",\"long\"]]", 444, "2013-04-24 04:54:44 UTC", "2014-04-24 04:54:44 UTC"] ] stub_api_request(:get, "/v3/table/list/#{e db_name}"). to_return(:body => {'tables' => [ {'name' => tables[0][0], 'type' => tables[0][1], 'schema' => tables[0][2], 'count' => tables[0][3], 'created_at' => tables[0][4], 'updated_at' => tables[0][5]}, {'name' => tables[1][0], 'type' => tables[1][1], 'schema' => tables[1][2], 'count' => tables[1][3], 'created_at' => tables[1][4], 'updated_at' => tables[1][5]}, {'name' => tables[2][0], 'type' => tables[2][1], 'schema' => tables[2][2], 'count' => tables[2][3], 'created_at' => tables[2][4], 'updated_at' => tables[2][5]}, {'name' => tables[3][0], 'type' => tables[3][1], 'schema' => tables[3][2], 'count' => tables[3][3], 'created_at' => tables[3][4], 'updated_at' => tables[3][5]} ]}.to_json) table_list = api.list_tables(db_name) tables.each {|table| expect(table_list[table[0]][0]).to eq(table[1].to_sym) expect(table_list[table[0]][1]).to eq(JSON.parse(table[2])) expect(table_list[table[0]][2]).to eq(table[3]) expect(table_list[table[0]][3]).to eq(table[4]) expect(table_list[table[0]][4]).to eq(table[5]) } end end describe "'tables' Client API" do it 'should return an array of Table objects' do tables = [ ["table_1", "item", "[[\"value\",\"string\"]]", 111, "2013-01-21 01:51:41 UTC", "2014-01-21 01:51:41 UTC"], ["table_2", "log", "[[\"value\",\"long\"]]", 222, "2013-02-22 02:52:42 UTC", "2014-02-22 02:52:42 UTC"], ["table_3", "item", "[[\"value\",\"string\"]]", 333, "2013-03-23 03:53:43 UTC", "2014-03-23 03:53:43 UTC"], ["table_4", "log", "[[\"value\",\"long\"]]", 444, "2013-04-24 04:54:44 UTC", "2014-04-24 04:54:44 UTC"] ] stub_api_request(:get, "/v3/table/list/#{e db_name}"). to_return(:body => {'tables' => [ {'name' => tables[0][0], 'type' => tables[0][1], 'schema' => tables[0][2], 'count' => tables[0][3], 'created_at' => tables[0][4], 'updated_at' => tables[0][5]}, {'name' => tables[1][0], 'type' => tables[1][1], 'schema' => tables[1][2], 'count' => tables[1][3], 'created_at' => tables[1][4], 'updated_at' => tables[1][5]}, {'name' => tables[2][0], 'type' => tables[2][1], 'schema' => tables[2][2], 'count' => tables[2][3], 'created_at' => tables[2][4], 'updated_at' => tables[2][5]}, {'name' => tables[3][0], 'type' => tables[3][1], 'schema' => tables[3][2], 'count' => tables[3][3], 'created_at' => tables[3][4], 'updated_at' => tables[3][5]} ]}.to_json) table_list = client.tables(db_name).sort_by { |e| e.name } db_count = 0 tables.each {|table| db_count += table[3] } # REST API call to fetch the database permission stub_api_request(:get, "/v3/database/list"). to_return(:body => {'databases' => [ {'name' => db_name, 'count' => db_count, 'created_at' => tables[0][4], 'updated_at' => tables[0][5], 'permission' => 'full_access'} ]}.to_json) tables.length.times {|i| expect(table_list[i].db_name).to eq(db_name) expect(table_list[i].name).to eq(tables[i][0]) expect(table_list[i].type).to eq(tables[i][1].to_sym) expect(table_list[i].schema.to_json).to eq(eval(tables[i][2]).to_json) expect(table_list[i].count).to eq(tables[i][3]) expect(table_list[i].created_at).to eq(Time.parse(tables[i][4])) expect(table_list[i].updated_at).to eq(Time.parse(tables[i][5])) # REST API call to fetch the database permission stub_api_request(:get, "/v3/database/list"). to_return(:body => {'databases' => [ {'name' => db_name, 'count' => db_count, 'created_at' => tables[0][4], 'updated_at' => tables[0][5], 'permission' => 'full_access'} ]}.to_json) expect(table_list[i].permission).to eq(:full_access) # set up a trap to check this call never happens # - if it did, the next assertion on the count would fail stub_api_request(:get, "/v3/database/list"). to_return(:body => {'databases' => [ {'name' => db_name, 'count' => db_count + 100, 'created_at' => tables[0][4], 'updated_at' => tables[0][5], 'permission' => 'full_access'} ]}.to_json) expect(table_list[i].database.count).to eq(db_count) } end end describe "'table' Client API" do it 'should return the Table object corresponding to the name' do tables = [ ["table_1", "item", "[[\"value\",\"string\"]]", 111, "2013-01-21 01:51:41 UTC", "2014-01-21 01:51:41 UTC"], ["table_2", "log", "[[\"value\",\"long\"]]", 222, "2013-02-22 02:52:42 UTC", "2014-02-22 02:52:42 UTC"], ["table_3", "item", "[[\"value\",\"string\"]]", 333, "2013-03-23 03:53:43 UTC", "2014-03-23 03:53:43 UTC"], ["table_4", "log", "[[\"value\",\"long\"]]", 444, "2013-04-24 04:54:44 UTC", "2014-04-24 04:54:44 UTC"] ] stub_api_request(:get, "/v3/table/list/#{e db_name}"). to_return(:body => {'tables' => [ {'name' => tables[0][0], 'type' => tables[0][1], 'schema' => tables[0][2], 'count' => tables[0][3], 'created_at' => tables[0][4], 'updated_at' => tables[0][5]}, {'name' => tables[1][0], 'type' => tables[1][1], 'schema' => tables[1][2], 'count' => tables[1][3], 'created_at' => tables[1][4], 'updated_at' => tables[1][5]}, {'name' => tables[2][0], 'type' => tables[2][1], 'schema' => tables[2][2], 'count' => tables[2][3], 'created_at' => tables[2][4], 'updated_at' => tables[2][5]}, {'name' => tables[3][0], 'type' => tables[3][1], 'schema' => tables[3][2], 'count' => tables[3][3], 'created_at' => tables[3][4], 'updated_at' => tables[3][5]} ]}.to_json) i = 1 table = client.table(db_name, tables[i][0]) expect(table.name).to eq(tables[i][0]) expect(table.type).to eq(tables[i][1].to_sym) expect(table.schema.to_json).to eq(eval(tables[i][2]).to_json) expect(table.count).to eq(tables[i][3]) expect(table.created_at).to eq(Time.parse(tables[i][4])) expect(table.updated_at).to eq(Time.parse(tables[i][5])) end end describe 'swap_table' do it 'should swap tables' do stub_api_request(:post, '/v3/table/swap/db/table1/table2') expect(api.swap_table('db', 'table1', 'table2')).to eq(true) end end describe 'update_expire' do it 'should update expiry days' do stub_api_request(:post, '/v3/table/update/db/table'). with(:body => {'expire_days' => '5'}). to_return(:body => {'type' => 'type'}.to_json) expect(api.update_expire('db', 'table', 5)).to eq(true) end end describe 'handle include_v' do it 'should set/unset include_v flag' do stub_api_request(:get, '/v3/table/list/db'). to_return(:body => {'tables' => [ {'name' => 'table', 'type' => 'log', 'include_v' => true}, ]}.to_json) table = client.table('db', 'table') expect(table.include_v).to eq true stub_api_request(:get, '/v3/table/list/db'). to_return(:body => {'tables' => [ {'name' => 'table', 'type' => 'log', 'include_v' => false}, ]}.to_json) stub_api_request(:post, '/v3/table/update/db/table'). with(:body => {'include_v' => "false"}). to_return(:body => {"database"=>"db","table"=>"table","type"=>"log"}.to_json) api.update_table('db', 'table', include_v: "false") table = client.table('db', 'table') expect(table.include_v).to eq false end end describe 'tail' do let :packed do s = StringIO.new pk = MessagePack::Packer.new(s) pk.write([1, 2, 3]) pk.write([4, 5, 6]) pk.flush s.string end it 'yields row if block given' do stub_api_request(:get, '/v3/table/tail/db/table'). with(:query => {'format' => 'msgpack', 'count' => '10'}). to_return(:body => packed) result = [] api.tail('db', 'table', 10) do |row| result << row end expect(result).to eq([[1, 2, 3], [4, 5, 6]]) end it 'returns rows' do stub_api_request(:get, '/v3/table/tail/db/table'). with(:query => {'format' => 'msgpack', 'count' => '10'}). to_return(:body => packed) expect(api.tail('db', 'table', 10)).to eq([[1, 2, 3], [4, 5, 6]]) end it 'shows deprecated warning for from and to' do stub_api_request(:get, '/v3/table/tail/db/table'). with(:query => {'format' => 'msgpack', 'count' => '10'}). to_return(:body => packed) r, w = IO.pipe begin backup = $stderr.dup $stderr.reopen(w) api.tail('db', 'table', 10, 100, 0) ensure $stderr.reopen(backup) w.close end expect(r.read).to eq(%Q(parameter "to" and "from" no longer work\n)) end end describe 'change_database' do it 'should change the database belonging to' do stub_api_request(:post, "/v3/table/change_database/src_db/table"). with(:body => {'dest_database_name' => 'dst_db'}). to_return(:body => {'database' => 'dst_db', 'table' => 'table', 'type' => 'log'}.to_json) expect(api.change_database('src_db', 'table', 'dst_db')).to be true end it 'should return 403 error if dest database is inaccessible' do err_msg = 'Access denied for the destination' stub_api_request(:post, "/v3/table/change_database/src_db/table"). with(:body => {'dest_database_name' => 'inaccessible_db'}). to_return(:status => 403, :body => {'message' => err_msg}.to_json) expect { api.change_database('src_db', 'table', 'inaccessible_db') }.to raise_error(TreasureData::ForbiddenError, /#{err_msg}/) end it 'should return 403 error if there is a same name table in the dest database' do err_msg = 'Table table already exists in the destination' stub_api_request(:post, "/v3/table/change_database/src_db/table"). with(:body => {'dest_database_name' => 'dst_db'}). to_return(:status => 403, :body => {'message' => err_msg}.to_json) expect { api.change_database('src_db', 'table', 'dst_db') }.to raise_error(TreasureData::ForbiddenError, /#{err_msg}/) end it 'should return 404 error if the dest database does not exist' do err_msg = 'Destination database is not found' stub_api_request(:post, "/v3/table/change_database/src_db/table"). with(:body => {'dest_database_name' => 'notexist_db'}). to_return(:status => 404, :body => {'message' => err_msg}.to_json) expect { api.change_database('src_db', 'table', 'notexist_db') }.to raise_error(TreasureData::NotFoundError, /#{err_msg}/) end end end