はじめに
hkob の雑記録の第162回目は、File upload object の作成までをテストします。
FileUploadObject のテスト
まず、FileUploadObject のテストを記述しました。とりあえずテストデータを作成している small file だけテストします。fname で既存のファイル名を指定したら API で File upload object を作成し、その ID を保持します。
# frozen_string_literal: true module NotionRubyMapping RSpec.describe FileUploadObject do tc = TestConnection.instance tc.read_json "create_file_upload_image" describe "initialize" do context "with a small file" do subject { FileUploadObject.new fname: "spec/fixtures/ErSxuLeq.png-medium.png" } it "creates a new FileUploadObject" do expect(subject).to be_a(FileUploadObject) expect(subject.id).to eq "20cd8e4e-98ab-81aa-973b-00b23083c115" end end end end end
FileUploadObject#initialize
これだけであれば実装は簡単なので、さっと書いてしまいます。external_url の場合やサイズの大きいファイルの場合も記述してしまっています。
# frozen_string_literal: true class FileUploadObject MAX_SIZE = 10 * 1024 * 1024 # 10 MB # @param [String] id def initialize(fname:, external_url: nil) @fname = fname if external_url payload = {mode: "external_url", external_url: external_url, filename: fname} create payload else raise StandardError, "FileUploadObject requires a valid file name: #{fname}" unless File.exist?(fname) @file_size = File.size fname @number_of_parts = (@file_size - 1) / MAX_SIZE + 1 payload = @number_of_parts == 1 ? {} : {number_of_parts: @number_of_parts, mode: "multi_part", filename: @fname} create payload # single_file_upload if @number_of_parts == 1 end end attr_reader :id # @return [FileUploadObject] def create(payload) nc = NotionRubyMapping::NotionCache.instance response = nc.create_file_upload_request(payload) @id = response[:id] end end
NotionCache#create_file_upload_request
ここで create_file_upload_request を実装します。path が異なるだけで他のものと変わりませんので、特にテストは記載していません。
# @param [Hash] payload # @return [Hash] response def create_file_upload_request(payload = {}) request :post, file_uploads_path, payload end
file_uploads_path のテスト
file_uploads_path は
describe "file_uploads_path" do it { expect(nc.file_uploads_path).to eq "v1/file_uploads" } end
NotionCache#file_uploads_path
実装は簡単ですね。
def file_uploads_path "v1/file_uploads" end
WebMock の設定
テストでは実際に Web アクセスをするのではなく、WebMock を使っているのでこのままでは Mock が存在しないためにエラーになります。以下の記述により、stub が作成されます。
def create_file_upload generate_stubs_sub :post, __method__, :file_uploads_path, { image: [nil, 200], } end
ちなみに、generate_stubs_sub は以下のようになっています。今回の場合、メソッド名から得た prefix として create_file_upload と hash のキーから得た image を使って、昨日作成した create_file_upload_image というファイル名を得るようになっています。
# @param [Symbol] method # @param [Symbol, nil] prefix # @param [Symbol] path_method # @param [Hash<Array>] hash def generate_stubs_sub(method, prefix, path_method, hash) hash.each do |key, (id, code, payload)| raise StandardError, "code is missing" unless code.is_a? Integer path = id ? @nc.send(path_method, *id) : @nc.send(path_method) stub method, "#{prefix}_#{key}", path, code, payload end end
この中の stub は実際の Notion API の呼び出しのフリをする部分です。呼び出しの代わりに用意した json を返します。明日はこの部分を multipart に対応させる必要がありそうです。
# @param [Symbol] method # @param [String] json_file response body # @param [String] path # @param [Fixnum] code # @param [Hash] payload request body def stub(method, json_file, path, code, payload = nil) request = { headers: { "Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "Authorization" => "Bearer #{notion_token}", "Notion-Version" => NotionRubyMapping::NOTION_VERSION, "User-Agent" => "Faraday v2.13.1", }, } request[:body] = JSON.generate(payload) if payload response = { body: JSON.generate(read_json(json_file)), status: code, headers: { "Content-Type" => "application/json", }, } WebMock.stub_request(method, "https://api.notion.com/#{path}").with(request).to_return(response) end
テスト確認
ここまで記述したところ、無事にテストは通過しました。
FileUploadObject initialize with a small file creates a new FileUploadObject Finished in 0.00603 seconds (files took 0.30782 seconds to load) 1 example, 0 failures
おわりに
FileUploadObject の作成はしましたが、まだファイルのアップロードは実装していません。ここで、 # single_file_upload if @number_of_parts == 1 の部分を有効にしたいところですが、WebMock の部分や NotionCache の部分で multipart content-type の対応が必要なので、大きく修正が必要なようです。今日はここまでにしておきます。