はじめに
hkob の雑記録の第167回目は、ページアイコンをアップロードする set_icon をテスト・実装します。
FileObject のコンストラクタの修正
FileUploadObject を FileObject に登録できるようにするために、コンストラクタのテストを追加します。今回、File のアップロード部分までテストするのは面倒なので、FileUploadObject は instance_double で用意します。
let(:file_upload_object) { instance_double(FileUploadObject, id: TestConnection::FILE_UPLOAD_IMAGE_ID) }
テストは以下のようになりました。
describe "constructor" do context "with url" do (中略) end context "with file_upload_object" do subject { FileObject.new file_upload_object: file_upload_object } it { expect(subject.file_upload_object).to eq file_upload_object } it { expect(subject.type).to eq :file_upload } it { expect(subject.will_update).to be_falsey } end context "with json" do (中略) end context "without arguments" do (中略) end en
実装を以下のように修正しています。file_upload_object の引数が追加されています。
module NotionRubyMapping # TextObject class FileObject # @param [String] url # @return [TextObject] def initialize(url: nil, file_upload_object: nil, json: {}) if url @type = :external @url = url elsif file_upload_object @type = :file_upload @file_upload_object = file_upload_object elsif json @type = json[:type].to_sym @url = json[@type][:url] @expiry_time = json[@type][:expiry_time] else raise StandardError, "FileObject requires url: or json:" end @will_update = false end attr_reader :will_update, :url, :type, :file_upload_object
FileObject.file_object の修正
FileObject には file_object というクラスメソッドがあります。これは、引数が String の場合には URL として、FileObject の場合にはそのものを返却するメソッドでした。今回は、ここに FileUploadObject が渡された時に、これを内部に保持する FileObject を作成し、返却するようにします。テストは以下のようになりました。ここで、file_upload_object は FileUploadObject ではなく、その mock なので、is_a? に対する stub を用意しています。
describe "self.file_object" do subject { FileObject.file_object fo } context "with url string" do (中略) end context "with FileUploadObject" do let(:fo) { file_upload_object } before { allow(file_upload_object).to receive(:is_a?).with(FileUploadObject).and_return(true) } it { expect(subject).to be_is_a FileObject } it { expect(subject.file_upload_object).to eq file_upload_object } end context "with FileObject" do (中略) end end
実装は以下のようになりました。
# @param [FileObject, FileUploadObject, String] url_or_fuo_or_fo # @return [FileObject] self or created FileObject # @see https://www.notion.so/hkob/FileObject-6218c354e985423a90904f47a985be33#54b37c567e1d4dfcab06f6d8f8fd412e def self.file_object(url_or_fuo_or_fo) if url_or_fuo_or_fo.is_a? FileUploadObject FileObject.new file_upload_object: url_or_fuo_or_fo elsif url_or_fuo_or_fo.is_a? FileObject url_or_fuo_or_fo else FileObject.new url: url_or_fuo_or_fo end end
FileObject#property_values_json の修正
作成した FileObject から JSON を作成する部分も当然ながら修正が必要です。以下のテストを追加しています。
describe "property_values_json" do context "when internal image" do (中略) end context "when external image" do (中略) end context "when file upload object" do let(:target) { FileObject.new file_upload_object: file_upload_object } it_behaves_like "property values json", { type: "file_upload", file_upload: { id: TestConnection::FILE_UPLOAD_IMAGE_ID, }, } end end
実装は以下のようになりました。
# @return [Hash] def property_values_json if @type == :file_upload { type: @type.to_s, @type => { id: @file_upload_object.id, }, } else ans = { type: @type.to_s, @type => { url: @url, }, } ans[@type][:expiry_time] = @expiry_time if @expiry_time ans end end
FileObject#file_upload_object= の修正
FileObject の最後は、オブジェクトに後から FileUploadObject を追加する file_upload_object= です。
describe "file_upload_object=" do let(:target) { FileObject.new json: file_internal_json } before { target.file_upload_object = file_upload_object } it_behaves_like "property values json", { type: "file_upload", file_upload: { id: TestConnection::FILE_UPLOAD_IMAGE_ID, }, } it { expect(target.file_upload_object).to eq file_upload_object } end
以下のような実装を追加しました。
# @param [FileUploadObject] fuo def file_upload_object=(fuo) @file_upload_object = fuo @type = :file_upload @url = nil @expiry_time = nil @will_update = true end
FileObject のテスト
ここまでの修正におけるテスト結果は以下のようになります。
20:50:39 - INFO - Running: spec/notion_ruby_mapping/objects/file_object_spec.rb NotionRubyMapping::FileObject constructor with url is expected to eq "https://example.com/external.jpg" is expected to eq :external is expected to be falsey with file_upload_object is expected to eq #<InstanceDouble(FileUploadObject) (anonymous)> is expected to eq :file_upload is expected to be falsey with json is expected to eq "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/f7b6864c-f809-498d-8725-03fc7e85a9ff/nr....83da3411466cead5f144b5a955ea5be1844ec06c6893689a3fb86c369e2&X-Amz-SignedHeaders=host&x-id=GetObject" is expected to eq :file is expected to be falsey without arguments is expected to raise StandardError self.file_object with url string is expected to be is a NotionRubyMapping::FileObject is expected to eq "https://example.com/external.jpg" with FileUploadObject is expected to be is a NotionRubyMapping::FileObject is expected to eq #<InstanceDouble(FileUploadObject) (anonymous)> with FileObject is expected to be is a NotionRubyMapping::FileObject is expected to eq "https://example.com/external.jpg" property_values_json when internal image behaves like property values json is expected to eq {file: {expiry_time: "2022-03-10T00:56:24.105Z", url: "https://s3.us-west-2.amazonaws.com/secure.noti...f144b5a955ea5be1844ec06c6893689a3fb86c369e2&X-Amz-SignedHeaders=host&x-id=GetObject"}, type: "file"} when external image behaves like property values json is expected to eq {external: {url: "https://img.icons8.com/ios-filled/250/000000/mac-os.png"}, type: "external"} when file upload object behaves like property values json is expected to eq {file_upload: {id: "20cd8e4e98ab81aa973b00b23083c115"}, type: "file_upload"} create from json when internal image behaves like property values json is expected to eq {file: {expiry_time: "2022-03-10T00:56:24.105Z", url: "https://s3.us-west-2.amazonaws.com/secure.noti...f144b5a955ea5be1844ec06c6893689a3fb86c369e2&X-Amz-SignedHeaders=host&x-id=GetObject"}, type: "file"} when external image is expected to eq "https://img.icons8.com/ios-filled/250/000000/mac-os.png" behaves like property values json is expected to eq {external: {url: "https://img.icons8.com/ios-filled/250/000000/mac-os.png"}, type: "external"} url= when internal image is expected to eq "https://example.com/external.jpg" behaves like property values json is expected to eq {external: {url: "https://example.com/external.jpg"}, type: "external"} when external image is expected to eq "https://example.com/external.jpg" behaves like property values json is expected to eq {external: {url: "https://example.com/external.jpg"}, type: "external"} file_upload_object= is expected to eq #<InstanceDouble(FileUploadObject) (anonymous)> behaves like property values json is expected to eq {file_upload: {id: "20cd8e4e98ab81aa973b00b23083c115"}, type: "file_upload"} Finished in 0.01339 seconds (files took 0.42259 seconds to load) 28 examples, 0 failures
Payload#set_icon の修正
次に Payload の set_icon メソッドの修正です。
describe "set_icon" do before { payload.set_icon(**params) } context "with emoji icon" do (中略) end context "with link icon" do (中略) end context "with file upload object" do let(:id) { TestConnection::FILE_UPLOAD_IMAGE_ID } let(:file_upload_object) { instance_double(FileUploadObject, id: id) } let(:params) { {file_upload_object: file_upload_object} } it "update icon (file upload)" do expect(subject).to eq({icon: {type: "file_upload", file_upload: {id: id}}}) end end end
実装は以下のようになりました。
# @param [String] emoji # @param [String] url # @param [FileUploadObject] file_upload_object # @return [NotionRubyMapping::Payload] updated Payload def set_icon(emoji: nil, url: nil, file_upload_object: nil) payload = if emoji {type: "emoji", emoji: emoji} elsif url {type: "external", external: {url: url}} elsif file_upload_object {type: "file_upload", file_upload: {id: file_upload_object.id}} else {} end @json[:icon] = payload self end
テスト結果は以下のようになりました。
21:03:05 - INFO - Running: spec/notion_ruby_mapping/controllers/payload_spec.rb NotionRubyMapping::Payload constructor can create an object description= is expected to eq {description: [{href: nil, plain_text: "Title", text: {content: "Title", link: nil}, type: "text"}]} is_inline= is expected to eq {is_inline: true} set_icon with emoji icon update icon (emoji) with link icon update icon (link) with file upload object update icon (file upload) Finished in 0.05328 seconds (files took 0.29941 seconds to load) 6 examples, 0 failures
Page#set_icon の修正
最後に今回の目的である Page の set_icon のテストを修正します。FileUploadObject の icon で save しますが、Page object 自体は返却された JSON で上書きされるため、internal_file の FileObject に変換して保存されます。
describe "update_icon" do let(:target) { Page.new id: TestConnection::TOP_PAGE_ID } before do target.set_icon(**params) end subject { target.icon } context "with emoji icon" do (中略) end context "with link icon" do (中略) end context "with file upload object" do let(:id) { TestConnection::FILE_UPLOAD_IMAGE_ID } let(:file_upload_object) { instance_double(FileUploadObject, id: id) } let(:params) { {file_upload_object: file_upload_object} } describe "dry_run" do let(:dry_run) { target.save dry_run: true } it_behaves_like "dry run", :patch, :page_path, use_id: true, json_method: :property_values_json end describe "save" do before { target.save } it "update icon (file upload object)" do expect(subject).to eq( { file: {
テスト結果は以下のようになります。Page はテストが多いので、該当箇所のみ示します。
21:04:54 - INFO - Running: spec/notion_ruby_mapping/blocks/page_spec.rb NotionRubyMapping::Page (中略) set_icon with emoji icon dry_run behaves like dry run is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/pages/c01166c613ae45cbb96818b4ef2f5a77' \\\n -H... -H 'Content-Type: application/json' \\\n --data '{\"icon\":{\"type\":\"emoji\",\"emoji\":\"😀\"}}'" save update icon (emoji) with link icon dry_run behaves like dry run is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/pages/c01166c613ae45cbb96818b4ef2f5a77' \\\n -H...rnal\",\"external\":{\"url\":\"https://cdn.profile-image.st-hatena.com/users/hkob/profile.png\"}}}'" save update icon (link) with file upload object dry_run behaves like dry run is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/pages/c01166c613ae45cbb96818b4ef2f5a77' \\\n -H...\"icon\":{\"type\":\"file_upload\",\"file_upload\":{\"id\":\"20cd8e4e98ab81aa973b00b23083c115\"}}}'" save update icon (file upload object)
おわりに
FileUploadObject が生成でき、今回それを含む FileObject の実装が完了したので、後はそれらを使ったテストと実装を追加していくだけです。急いで実装していきます。