From 6c0e72add69962dc79e1751db961b346efbfb2af Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 16 Jan 2024 10:22:13 -0500 Subject: [PATCH 01/15] Include vcr and webmock gems --- Gemfile | 2 ++ Gemfile.lock | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Gemfile b/Gemfile index 9c68128..4d09338 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,8 @@ gemspec gem "rake", "~> 13.0" gem "rspec", "~> 3.0" +gem "vcr" +gem "webmock" gem "rubocop", "~> 1.21" diff --git a/Gemfile.lock b/Gemfile.lock index 49fa53c..7d2b8f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,8 +6,13 @@ PATH GEM remote: https://rubygems.org/ specs: + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) + crack (0.4.5) + rexml diff-lcs (1.5.0) + hashdiff (1.1.0) httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) @@ -19,6 +24,7 @@ GEM parser (3.3.0.3) ast (~> 2.4.1) racc + public_suffix (5.0.4) racc (1.7.3) rainbow (3.1.1) rake (13.1.0) @@ -52,6 +58,11 @@ GEM parser (>= 3.2.1.0) ruby-progressbar (1.13.0) unicode-display_width (2.5.0) + vcr (6.2.0) + webmock (3.19.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) PLATFORMS arm64-darwin-23 @@ -63,6 +74,8 @@ DEPENDENCIES rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.21) + vcr + webmock BUNDLED WITH 2.4.10 From 4b1ee919ca276ca69c5e3bbe8771cc36cb342677 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 16 Jan 2024 10:23:44 -0500 Subject: [PATCH 02/15] Improve some test descriptions --- spec/bns/dispatcher/base_spec.rb | 2 +- spec/bns/fetcher/base_spec.rb | 4 ++-- spec/bns/formatter/base_spec.rb | 2 +- spec/bns/mapper/base_spec.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/bns/dispatcher/base_spec.rb b/spec/bns/dispatcher/base_spec.rb index 32e628f..36a525c 100644 --- a/spec/bns/dispatcher/base_spec.rb +++ b/spec/bns/dispatcher/base_spec.rb @@ -15,7 +15,7 @@ end describe ".dispatch" do - it "provides no implementation of the method" do + it "provides no implementation for the method" do payload = "" expect { @dispatcher.dispatch(payload) }.to raise_exception("Not implemented yet.") end diff --git a/spec/bns/fetcher/base_spec.rb b/spec/bns/fetcher/base_spec.rb index 974dc2c..fa2e71a 100644 --- a/spec/bns/fetcher/base_spec.rb +++ b/spec/bns/fetcher/base_spec.rb @@ -15,13 +15,13 @@ end describe ".fetch" do - it "provides no implementation of the method" do + it "provides no implementation for the method" do expect { @fetcher.fetch }.to raise_exception("Not implemented yet.") end end describe ".format_response" do - it "provides no implementation of the method" do + it "provides no implementation for the method" do response = {} expect { @fetcher.normalize_response(response) }.to raise_exception("Not implemented yet.") end diff --git a/spec/bns/formatter/base_spec.rb b/spec/bns/formatter/base_spec.rb index 9517596..03e518b 100644 --- a/spec/bns/formatter/base_spec.rb +++ b/spec/bns/formatter/base_spec.rb @@ -6,7 +6,7 @@ describe ".format" do let(:testing_class) { Class.new { include Formatter::Base } } - it "provides no implementation of the method" do + it "provides no implementation for the method" do instace = testing_class.new data = [] expect { instace.format(data) }.to raise_exception("Not implemented yet.") diff --git a/spec/bns/mapper/base_spec.rb b/spec/bns/mapper/base_spec.rb index c8b76de..40f7653 100644 --- a/spec/bns/mapper/base_spec.rb +++ b/spec/bns/mapper/base_spec.rb @@ -6,7 +6,7 @@ describe ".format" do let(:testing_class) { Class.new { include Mapper::Base } } - it "provides no implementation of the method" do + it "provides no implementation for the method" do instace = testing_class.new data = [] expect { instace.map(data) }.to raise_exception("Not implemented yet.") From f8777f3394f795bf9e52f37e020d948ac99c0c27 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 16 Jan 2024 10:27:37 -0500 Subject: [PATCH 03/15] Include vcr config for rspec --- spec/spec_helper.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d21d52..cb0b1bb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true require "bns" +require "vcr" + +VCR.configure do |config| + config.cassette_library_dir = "spec/fixtures/vcr_cassettes" + config.hook_into :webmock +end RSpec.configure do |config| # Enable flags like --only-failures and --next-failure @@ -12,4 +18,6 @@ config.expect_with :rspec do |c| c.syntax = :expect end + + # config.include Fetcher::Configuration, type: :feature end From 9780fe75d57bda8fef94a36bee9e18d8bf40f0a6 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 16 Jan 2024 10:44:33 -0500 Subject: [PATCH 04/15] Include tests for the notion fetcher and the respective vcr cassettes --- spec/bns/fetcher/birthday/notion_spec.rb | 79 +++++++++++++++++++ .../notion_birthdays_no_filter.yml | 76 ++++++++++++++++++ .../notion_birthdays_with_filter.yml | 60 ++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 spec/bns/fetcher/birthday/notion_spec.rb create mode 100644 spec/fixtures/vcr_cassettes/notion_birthdays_no_filter.yml create mode 100644 spec/fixtures/vcr_cassettes/notion_birthdays_with_filter.yml diff --git a/spec/bns/fetcher/birthday/notion_spec.rb b/spec/bns/fetcher/birthday/notion_spec.rb new file mode 100644 index 0000000..d1a92d8 --- /dev/null +++ b/spec/bns/fetcher/birthday/notion_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +RSpec.describe Fetcher::Birthday::Notion do + before do + @config = { + base_url: "https://api.notion.com", + database_id: "c17e556d16c84272beb4ee73ab709631", + secret: "secret_BELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51B" + } + + @fetcher = described_class.new(@config) + end + + describe "Arguments and methods" do + it { expect(@fetcher).to respond_to(:config) } + + it { expect(described_class).to respond_to(:new).with(1).arguments } + it { expect(@fetcher).to respond_to(:fetch).with(0).arguments } + it { expect(@fetcher).to respond_to(:normalize_response).with(1).arguments } + end + + describe ".fetch" do + it "fetch data from the given configured notion database" do + VCR.use_cassette("notion_birthdays_no_filter") do + expected = [ + { "name" => "Laura Villada", "birth_date" => "2024-01-31" }, + { "name" => "Luz Maria Quintero", "birth_date" => "2024-01-12" }, + { "name" => "Juan Hurtado", "birth_date" => "2024-01-29" }, + { "name" => "Lorenzo Zuluaga", "birth_date" => "2024-01-15" }, + { "name" => "Luis Hurtado", "birth_date" => "2024-01-15" } + ] + + birthdays_fetcher = described_class.new(@config) + fetched_data = birthdays_fetcher.fetch + + expect(fetched_data).to be_an_instance_of(Array) + expect(fetched_data.length).to eq(5) + expect(fetched_data).to match_array(expected) + end + end + + it "fetch data from the given configured notion database using the provided filter" do + VCR.use_cassette("notion_birthdays_with_filter") do + today = DateTime.now.strftime("%F").to_s + + config = @config.merge( + { + filter: { + "filter": { + "or": [ + { + "property": "BD_this_year", + "date": { + "equals": today + } + } + ] + }, + "sorts": [] + } + } + ) + + expected = [ + { "name" => "Luis Hurtado", "birth_date" => "2024-01-16" } + ] + + birthdays_fetcher = described_class.new(config) + fetched_data = birthdays_fetcher.fetch + + expect(fetched_data).to be_an_instance_of(Array) + expect(fetched_data.length).to eq(1) + expect(fetched_data).to match_array(expected) + end + end + end + + # Confirm if the normalize function will be part of the fetcher, or we should handle that as a separate class/module +end diff --git a/spec/fixtures/vcr_cassettes/notion_birthdays_no_filter.yml b/spec/fixtures/vcr_cassettes/notion_birthdays_no_filter.yml new file mode 100644 index 0000000..11477d3 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/notion_birthdays_no_filter.yml @@ -0,0 +1,76 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.notion.com/v1/databases/c17e556d16c84272beb4ee73ab709631/query + body: + encoding: UTF-8 + string: "{}" + headers: + Authorization: + - Bearer secret_BELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51B + Content-Type: + - application/json + Notion-Version: + - '2022-06-28' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 16 Jan 2024 13:17:08 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + X-Powered-By: + - Express + X-Notion-Request-Id: + - c169d957-4b2b-4295-844d-aa1754ff6fc2 + Etag: + - W/"1783-3St9Y1mxbCzjXnFPSZt5EhHg+iI" + Vary: + - Accept-Encoding + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=C7XirQHgLDYUZSv3EVyPYdzopLn_FF1vNfaZj7nDSag-1705411028-1-AfvONDKWf2pahxQds6l6T+oZX4rSdKEb1jftlHxhpvXict+AA04jWCfRd3nWQTQBPEKPoUhrUXQFJpC8z5Ggt74=; + path=/; expires=Tue, 16-Jan-24 13:47:08 GMT; domain=.notion.com; HttpOnly; + Secure; SameSite=None + Server: + - cloudflare + Cf-Ray: + - 8466a30b3bfcf79c-BOG + body: + encoding: ASCII-8BIT + string: '{"object":"list","results":[{"object":"page","id":"3f9e55d5-1bb3-47a2-aaf0-dd07e45f8266","created_time":"2024-01-12T14:53:00.000Z","last_edited_time":"2024-01-12T14:53:00.000Z","created_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"last_edited_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"cover":null,"icon":null,"parent":{"type":"database_id","database_id":"c17e556d-16c8-4272-beb4-ee73ab709440"},"archived":false,"properties":{"Full + Name":{"id":"CkmY","type":"rich_text","rich_text":[{"type":"text","text":{"content":"Laura + Villada","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Laura + Villada","href":null}]},"BD_this_year":{"id":"Qo%3Cj","type":"date","date":{"start":"2024-01-31","end":null,"time_zone":null}},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"Laura + Villada","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Laura + Villada","href":null}]}},"url":"https://www.notion.so/Laura-Villada-3f9e55d51bb347a2aaf0dd07e45f8266","public_url":null},{"object":"page","id":"1e7d9b28-d186-4f2e-9967-36c319cc77da","created_time":"2024-01-12T05:11:00.000Z","last_edited_time":"2024-01-12T05:11:00.000Z","created_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"last_edited_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"cover":null,"icon":null,"parent":{"type":"database_id","database_id":"c17e556d-16c8-4272-beb4-ee73ab709440"},"archived":false,"properties":{"Full + Name":{"id":"CkmY","type":"rich_text","rich_text":[{"type":"text","text":{"content":"Luz + Maria Quintero","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Luz + Maria Quintero","href":null}]},"BD_this_year":{"id":"Qo%3Cj","type":"date","date":{"start":"2024-01-12","end":null,"time_zone":null}},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"Luzma","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Luzma","href":null}]}},"url":"https://www.notion.so/Luzma-1e7d9b28d1864f2e996736c319cc77da","public_url":null},{"object":"page","id":"2187c3fe-346a-4c76-b51a-f3b95e268885","created_time":"2024-01-11T14:01:00.000Z","last_edited_time":"2024-01-12T03:29:00.000Z","created_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"last_edited_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"cover":null,"icon":null,"parent":{"type":"database_id","database_id":"c17e556d-16c8-4272-beb4-ee73ab709440"},"archived":false,"properties":{"Full + Name":{"id":"CkmY","type":"rich_text","rich_text":[{"type":"text","text":{"content":"Juan + Hurtado","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Juan + Hurtado","href":null}]},"BD_this_year":{"id":"Qo%3Cj","type":"date","date":{"start":"2024-01-29","end":null,"time_zone":null}},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"Juan + H","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Juan + H","href":null}]}},"url":"https://www.notion.so/Juan-H-2187c3fe346a4c76b51af3b95e268885","public_url":null},{"object":"page","id":"51309b7f-da9a-4685-9473-7e71f7bd9279","created_time":"2024-01-11T14:01:00.000Z","last_edited_time":"2024-01-15T19:54:00.000Z","created_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"last_edited_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"cover":null,"icon":null,"parent":{"type":"database_id","database_id":"c17e556d-16c8-4272-beb4-ee73ab709440"},"archived":false,"properties":{"Full + Name":{"id":"CkmY","type":"rich_text","rich_text":[{"type":"text","text":{"content":"Lorenzo + Zuluaga","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Lorenzo + Zuluaga","href":null}]},"BD_this_year":{"id":"Qo%3Cj","type":"date","date":{"start":"2024-01-15","end":null,"time_zone":null}},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"Lorenzo","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Lorenzo","href":null}]}},"url":"https://www.notion.so/Lorenzo-51309b7fda9a468594737e71f7bd9279","public_url":null},{"object":"page","id":"c05d68ca-dbff-4127-81ce-820f245d7061","created_time":"2024-01-11T14:01:00.000Z","last_edited_time":"2024-01-15T20:00:00.000Z","created_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"last_edited_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"cover":null,"icon":null,"parent":{"type":"database_id","database_id":"c17e556d-16c8-4272-beb4-ee73ab709440"},"archived":false,"properties":{"Full + Name":{"id":"CkmY","type":"rich_text","rich_text":[{"type":"text","text":{"content":"Luis + Hurtado","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Luis + Hurtado","href":null}]},"BD_this_year":{"id":"Qo%3Cj","type":"date","date":{"start":"2024-01-15","end":null,"time_zone":null}},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"LH","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"LH","href":null}]}},"url":"https://www.notion.so/LH-c05d68cadbff412781ce820f245d7061","public_url":null}],"next_cursor":null,"has_more":false,"type":"page_or_database","page_or_database":{},"request_id":"c169d957-4b2b-4295-844d-aa1754ff6fc2"}' + recorded_at: Tue, 16 Jan 2024 13:17:08 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/notion_birthdays_with_filter.yml b/spec/fixtures/vcr_cassettes/notion_birthdays_with_filter.yml new file mode 100644 index 0000000..4b35f87 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/notion_birthdays_with_filter.yml @@ -0,0 +1,60 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.notion.com/v1/databases/c17e556d16c84272beb4ee73ab709631/query + body: + encoding: UTF-8 + string: '{"filter":{"or":[{"property":"BD_this_year","date":{"equals":"2024-01-16"}}]},"sorts":[]}' + headers: + Authorization: + - Bearer secret_BELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51B + Content-Type: + - application/json + Notion-Version: + - '2022-06-28' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 16 Jan 2024 13:22:17 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + X-Powered-By: + - Express + X-Notion-Request-Id: + - 523b7c7d-31f7-4f8a-968a-7c32912b7eba + Etag: + - W/"526-7hIKltu2FaOOAbZpxAo7AeOegDU" + Vary: + - Accept-Encoding + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=RmtNYlws.Wq5HLTl5Fgfo1t2VziaetL0206ZcL.AahA-1705411337-1-Afr9IcVdyMM6BQPJyQvZ8MrrmSAUZbrf016APwnnvcvLjGp52+6YnGt+aSR0TUbG6geKnBsKat4DJIneC46y5ZM=; + path=/; expires=Tue, 16-Jan-24 13:52:17 GMT; domain=.notion.com; HttpOnly; + Secure; SameSite=None + Server: + - cloudflare + Cf-Ray: + - 8466aa94da02f7a0-BOG + body: + encoding: ASCII-8BIT + string: '{"object":"list","results":[{"object":"page","id":"c05d68ca-dbff-4127-81ce-820f245d7061","created_time":"2024-01-11T14:01:00.000Z","last_edited_time":"2024-01-16T13:20:00.000Z","created_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"last_edited_by":{"object":"user","id":"c6b8a1d7-ad21-4bad-92a9-6c5310ec825a"},"cover":null,"icon":null,"parent":{"type":"database_id","database_id":"c17e556d-16c8-4272-beb4-ee73ab709440"},"archived":false,"properties":{"Full + Name":{"id":"CkmY","type":"rich_text","rich_text":[{"type":"text","text":{"content":"Luis + Hurtado","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"Luis + Hurtado","href":null}]},"BD_this_year":{"id":"Qo%3Cj","type":"date","date":{"start":"2024-01-16","end":null,"time_zone":null}},"Name":{"id":"title","type":"title","title":[{"type":"text","text":{"content":"LH","link":null},"annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"plain_text":"LH","href":null}]}},"url":"https://www.notion.so/LH-c05d68cadbff412781ce820f245d7061","public_url":null}],"next_cursor":null,"has_more":false,"type":"page_or_database","page_or_database":{},"request_id":"523b7c7d-31f7-4f8a-968a-7c32912b7eba"}' + recorded_at: Tue, 16 Jan 2024 13:22:17 GMT +recorded_with: VCR 6.2.0 From 75fdeb7aeea3b3a907f8d42469dc6db7c0e51aa4 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Wed, 17 Jan 2024 08:38:22 -0500 Subject: [PATCH 05/15] Add missing validation for empty responses from fetcher --- lib/bns/fetcher/birthday/notion.rb | 1 + lib/bns/use_cases/use_case.rb | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/bns/fetcher/birthday/notion.rb b/lib/bns/fetcher/birthday/notion.rb index 593c858..789f6c3 100644 --- a/lib/bns/fetcher/birthday/notion.rb +++ b/lib/bns/fetcher/birthday/notion.rb @@ -22,6 +22,7 @@ def fetch end def normalize_response(response) + return [] if response == nil normalized_response = [] response.map do |value| diff --git a/lib/bns/use_cases/use_case.rb b/lib/bns/use_cases/use_case.rb index 3a73917..b6e29d4 100644 --- a/lib/bns/use_cases/use_case.rb +++ b/lib/bns/use_cases/use_case.rb @@ -14,11 +14,13 @@ def initialize(options) def perform response = fetcher.fetch - mappings = mapper.map(response) + if response.length > 0 + mappings = mapper.map(response) - formatted_payload = formatter.format(mappings) + formatted_payload = formatter.format(mappings) - dispatcher.dispatch(formatted_payload) + dispatcher.dispatch(formatted_payload) + end end end end From 391b2734675197a09a565ec11f9c05e8e00089a0 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Wed, 17 Jan 2024 09:10:33 -0500 Subject: [PATCH 06/15] Add test for fetcher empty responses --- spec/bns/fetcher/birthday/notion_spec.rb | 30 ++++++++++ .../notion_birthdays_with_empty_database.yml | 57 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml diff --git a/spec/bns/fetcher/birthday/notion_spec.rb b/spec/bns/fetcher/birthday/notion_spec.rb index d1a92d8..0e377af 100644 --- a/spec/bns/fetcher/birthday/notion_spec.rb +++ b/spec/bns/fetcher/birthday/notion_spec.rb @@ -73,6 +73,36 @@ expect(fetched_data).to match_array(expected) end end + + it "fetch empty data from the given configured notion database" do + VCR.use_cassette("notion_birthdays_with_empty_database") do + today = DateTime.now.strftime("%F").to_s + + config = @config.merge( + { + filter: { + "filter": { + "or": [ + { + "property": "BD_this_year", + "date": { + "equals": today + } + } + ] + }, + "sorts": [] + } + } + ) + + birthday_fetcher = described_class.new(config) + fetched_data = birthday_fetcher.fetch + + expect(fetched_data).to be_an_instance_of(Array) + expect(fetched_data.length).to eq(0) + end + end end # Confirm if the normalize function will be part of the fetcher, or we should handle that as a separate class/module diff --git a/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml b/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml new file mode 100644 index 0000000..65309b4 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml @@ -0,0 +1,57 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.notion.com/v1/databases/c17e556d16c84272beb4ee73ab709631/query + body: + encoding: UTF-8 + string: '{"filter":{"or":[{"property":"BD_this_year","date":{"equals":"2024-01-17"}}]},"sorts":[]}' + headers: + Authorization: + - Bearer secret_BELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51B + Content-Type: + - application/json + Notion-Version: + - '2022-06-28' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 17 Jan 2024 14:05:12 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + X-Powered-By: + - Express + X-Notion-Request-Id: + - 2273460d-6314-4034-bd38-ebcd6b7de9cd + Etag: + - W/"a6-pBkwSAFkoFXiQZy4hQZRE0W9e0M" + Vary: + - Accept-Encoding + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=Bn_eoqQD.XP4TzWSvNNE_QV9YUp_e_8iyyEayX60yHI-1705500312-1-AUxBcJVzS7atzYakV6Ck51uiXdb7XELzVow4W7OIeeH7T91Qry2XkpIYFZSdLTGqLwrtNb8hkff7u70AG6WBwv0=; + path=/; expires=Wed, 17-Jan-24 14:35:12 GMT; domain=.notion.com; HttpOnly; + Secure; SameSite=None + Server: + - cloudflare + Cf-Ray: + - 846f26d05852f792-BOG + body: + encoding: ASCII-8BIT + string: '{"object":"list","results":[],"next_cursor":null,"has_more":false,"type":"page_or_database","page_or_database":{},"request_id":"2273460d-6314-4034-bd38-ebcd6b7de9cd"}' + recorded_at: Wed, 17 Jan 2024 14:05:12 GMT +recorded_with: VCR 6.2.0 From 42e9945ab54509e8f2be960e45a199865d77bd3c Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Wed, 17 Jan 2024 10:09:46 -0500 Subject: [PATCH 07/15] Add enhancement to notion fetcher to handle errors --- .github/workflows/main.yml | 3 +-- lib/bns/fetcher/birthday/notion.rb | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca09e8a..c49f774 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,6 @@ on: jobs: build: - if: false runs-on: ubuntu-latest name: Ruby ${{ matrix.ruby }} strategy: @@ -18,7 +17,7 @@ jobs: - '3.2.2' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/lib/bns/fetcher/birthday/notion.rb b/lib/bns/fetcher/birthday/notion.rb index 789f6c3..3d37326 100644 --- a/lib/bns/fetcher/birthday/notion.rb +++ b/lib/bns/fetcher/birthday/notion.rb @@ -15,10 +15,10 @@ def fetch "Content-Type" => "application/json", "Notion-Version" => "2022-06-28" } - response = HTTParty.post(url, { body: config[:filter].to_json, headers: headers }) + validated_response = validate_response(response) - normalize_response(response["results"]) + normalize_response(validated_response["results"]) end def normalize_response(response) @@ -39,6 +39,20 @@ def normalize_response(response) private + def validate_response(response) + error_codes = [401, 404] + + begin + if error_codes.include?(response["status"]) + raise response["message"] + else + return response + end + rescue ArgumentError => e + puts "Fetcher::Birthday::Notion Error: #{e.message}" + end + end + def normalize(properties) normalized_value = {} From 7e56222c7db51545d70b870bb94b10cf7dac6329 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Wed, 17 Jan 2024 10:11:01 -0500 Subject: [PATCH 08/15] Add remaining tests for notion fetcher, and minor improvements --- spec/bns/fetcher/birthday/notion_spec.rb | 42 +++++++------ .../notion_birthdays_with_empty_database.yml | 2 +- .../notion_birthdays_with_invalid_api_key.yml | 58 ++++++++++++++++++ ...ion_birthdays_with_invalid_database_id.yml | 59 +++++++++++++++++++ 4 files changed, 142 insertions(+), 19 deletions(-) create mode 100644 spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_api_key.yml create mode 100644 spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_database_id.yml diff --git a/spec/bns/fetcher/birthday/notion_spec.rb b/spec/bns/fetcher/birthday/notion_spec.rb index 0e377af..01ae556 100644 --- a/spec/bns/fetcher/birthday/notion_spec.rb +++ b/spec/bns/fetcher/birthday/notion_spec.rb @@ -5,7 +5,8 @@ @config = { base_url: "https://api.notion.com", database_id: "c17e556d16c84272beb4ee73ab709631", - secret: "secret_BELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51B" + secret: "secret_BELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51B", + filter: {} } @fetcher = described_class.new(@config) @@ -78,23 +79,8 @@ VCR.use_cassette("notion_birthdays_with_empty_database") do today = DateTime.now.strftime("%F").to_s - config = @config.merge( - { - filter: { - "filter": { - "or": [ - { - "property": "BD_this_year", - "date": { - "equals": today - } - } - ] - }, - "sorts": [] - } - } - ) + config = @config + config[:database_id] = "w17e556d16c84272beb4ee73ab709639" birthday_fetcher = described_class.new(config) fetched_data = birthday_fetcher.fetch @@ -103,6 +89,26 @@ expect(fetched_data.length).to eq(0) end end + + it "provided database_id doesn't match any database" do + VCR.use_cassette("notion_birthdays_with_invalid_database_id") do + config = @config + config[:database_id] = "a17e556d16c84272beb4ee73ab709630" + birthday_fetcher = described_class.new(@config) + + expect { birthday_fetcher.fetch }.to raise_exception("Could not find database with ID: c17e556d-16c8-4272-beb4-ee73ab709631. Make sure the relevant pages and databases are shared with your integration.") + end + end + + it "provided api_key is invalid or incorrect" do + VCR.use_cassette("notion_birthdays_with_invalid_api_key") do + config = @config + config[:secret] = "secret_ZELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51C" + birthday_fetcher = described_class.new(config) + + expect { birthday_fetcher.fetch }.to raise_exception("API token is invalid.") + end + end end # Confirm if the normalize function will be part of the fetcher, or we should handle that as a separate class/module diff --git a/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml b/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml index 65309b4..4151706 100644 --- a/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml +++ b/spec/fixtures/vcr_cassettes/notion_birthdays_with_empty_database.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: post - uri: https://api.notion.com/v1/databases/c17e556d16c84272beb4ee73ab709631/query + uri: https://api.notion.com/v1/databases/w17e556d16c84272beb4ee73ab709639/query body: encoding: UTF-8 string: '{"filter":{"or":[{"property":"BD_this_year","date":{"equals":"2024-01-17"}}]},"sorts":[]}' diff --git a/spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_api_key.yml b/spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_api_key.yml new file mode 100644 index 0000000..47ec963 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_api_key.yml @@ -0,0 +1,58 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.notion.com/v1/databases/c17e556d16c84272beb4ee73ab709631/query + body: + encoding: UTF-8 + string: "{}" + headers: + Authorization: + - Bearer secret_ZELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51C + Content-Type: + - application/json + Notion-Version: + - '2022-06-28' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 401 + message: Unauthorized + headers: + Date: + - Wed, 17 Jan 2024 14:56:06 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '139' + Connection: + - keep-alive + X-Powered-By: + - Express + X-Notion-Request-Id: + - 3fd64030-cdaa-4782-afdc-7e487aa21a52 + Etag: + - W/"8b-/GHt+IYlFhwr6lypx5gz/qzKxUg" + Vary: + - Accept-Encoding + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=Xcdg7W9YmHS3.YUZfQDmkPtrnUrGUdgz1UrkBQIgXA0-1705503366-1-AWva0F9cfr9TMFYvR4b7isim3VDRZHcOGjKWFehDnMPMI0OfGKFRJwpWnJ2szdifzpvgootRPHV0Y3gmO7JzCk0=; + path=/; expires=Wed, 17-Jan-24 15:26:06 GMT; domain=.notion.com; HttpOnly; + Secure; SameSite=None + Server: + - cloudflare + Cf-Ray: + - 846f716309e4f776-BOG + body: + encoding: UTF-8 + string: '{"object":"error","status":401,"code":"unauthorized","message":"API + token is invalid.","request_id":"3fd64030-cdaa-4782-afdc-7e487aa21a52"}' + recorded_at: Wed, 17 Jan 2024 14:56:06 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_database_id.yml b/spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_database_id.yml new file mode 100644 index 0000000..1062664 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/notion_birthdays_with_invalid_database_id.yml @@ -0,0 +1,59 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.notion.com/v1/databases/a17e556d16c84272beb4ee73ab709630/query + body: + encoding: UTF-8 + string: "{}" + headers: + Authorization: + - Bearer secret_4x5x2CH9WKwhnptlUIJfI211CGyTTcgkjjcbr3AAO1q + Content-Type: + - application/json + Notion-Version: + - '2022-06-28' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 404 + message: Not Found + headers: + Date: + - Wed, 17 Jan 2024 14:43:47 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + X-Powered-By: + - Express + X-Notion-Request-Id: + - 165907d9-e7e2-4065-a4d1-8b093341a494 + Etag: + - W/"10d-O2IsXLC2cE+JazKrSf0o+AmQpS8" + Vary: + - Accept-Encoding + Cf-Cache-Status: + - DYNAMIC + Set-Cookie: + - __cf_bm=IJRUSEGuwcphQjLFn_zLs_d5RNHClrz8j_geJzQh5jE-1705502627-1-AbUg/XGTgQAeighj7GFvF0TjQJMOtQsNlp3VuFWlKyPkf5HHGOJBZSDtV2HFThYXmSuDBQxu/uDrSL4RPd66Pbs=; + path=/; expires=Wed, 17-Jan-24 15:13:47 GMT; domain=.notion.com; HttpOnly; + Secure; SameSite=None + Server: + - cloudflare + Cf-Ray: + - 846f5f5cdcc53eff-BOG + body: + encoding: ASCII-8BIT + string: '{"object":"error","status":404,"code":"object_not_found","message":"Could + not find database with ID: c17e556d-16c8-4272-beb4-ee73ab709631. Make sure + the relevant pages and databases are shared with your integration.","request_id":"165907d9-e7e2-4065-a4d1-8b093341a494"}' + recorded_at: Wed, 17 Jan 2024 14:43:47 GMT +recorded_with: VCR 6.2.0 From c0ce2e4152db2de8dc109982dba520174fa8445c Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Wed, 17 Jan 2024 14:33:38 -0500 Subject: [PATCH 09/15] Improve some of the fetcher test names --- spec/bns/fetcher/birthday/notion_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bns/fetcher/birthday/notion_spec.rb b/spec/bns/fetcher/birthday/notion_spec.rb index 01ae556..b4eda8b 100644 --- a/spec/bns/fetcher/birthday/notion_spec.rb +++ b/spec/bns/fetcher/birthday/notion_spec.rb @@ -90,7 +90,7 @@ end end - it "provided database_id doesn't match any database" do + it "with provided database_id not matching any database" do VCR.use_cassette("notion_birthdays_with_invalid_database_id") do config = @config config[:database_id] = "a17e556d16c84272beb4ee73ab709630" @@ -100,7 +100,7 @@ end end - it "provided api_key is invalid or incorrect" do + it "with invalid or incorrect api_key provided" do VCR.use_cassette("notion_birthdays_with_invalid_api_key") do config = @config config[:secret] = "secret_ZELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51C" From 3415942454857270348bd2991beaf8bae023ced0 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Thu, 18 Jan 2024 10:04:45 -0500 Subject: [PATCH 10/15] Add discord dispatcher tests and minor improvements to naming --- spec/bns/dispatcher/discord_spec.rb | 58 ++++++++++++++ spec/bns/fetcher/birthday/notion_spec.rb | 12 +-- ...iscord_failed_dispatch_invalid_webhook.yml | 76 +++++++++++++++++++ .../discord_success_dispatch.yml | 74 ++++++++++++++++++ .../discord_success_dispatch_empty_name.yml | 74 ++++++++++++++++++ 5 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 spec/bns/dispatcher/discord_spec.rb create mode 100644 spec/fixtures/vcr_cassettes/discord_failed_dispatch_invalid_webhook.yml create mode 100644 spec/fixtures/vcr_cassettes/discord_success_dispatch.yml create mode 100644 spec/fixtures/vcr_cassettes/discord_success_dispatch_empty_name.yml diff --git a/spec/bns/dispatcher/discord_spec.rb b/spec/bns/dispatcher/discord_spec.rb new file mode 100644 index 0000000..a6f61a0 --- /dev/null +++ b/spec/bns/dispatcher/discord_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +RSpec.describe Dispatcher::Discord do + before do + @config = { + webhook: "https://discord.com/api/webhooks/1196541734138691615/lFFCvFdMVEvfKWtFID2TSBjNjjBvEwqRbG2czOz3X_HfHfIgmXh6SDlFRXaXLOignsOj", + name: "Test Birthday Bot" + } + + @payload = "John Doe, Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:" + + @dispatcher = described_class.new(@config) + end + + describe "attributes and arguments" do + it { expect(@dispatcher).to respond_to(:webhook) } + it { expect(@dispatcher).to respond_to(:name) } + + it { expect(described_class).to respond_to(:new).with(1).arguments } + it { expect(@dispatcher).to respond_to(:dispatch).with(1).arguments } + end + + describe ".dispatch" do + it "dispatch a notification message to discord" do + VCR.use_cassette("discord_success_dispatch") do + discords_dispatcher = described_class.new(@config) + + response = discords_dispatcher.dispatch(@payload) + + expect(response.code).to eq(204) + end + end + + it "doest dispatch a notification message to discord" do + VCR.use_cassette("discord_success_dispatch_empty_name") do + discords_dispatcher = described_class.new(@config) + + response = discords_dispatcher.dispatch(@payload) + + expect(response.code).to eq(204) + end + end + + it "raises an exception caused by incorrect webhook provided" do + VCR.use_cassette("discord_failed_dispatch_invalid_webhook") do + config = @config + config[:webhook] = "https://discord.com/api/webhooks/1196541734138691615/lFFCvFdMVEvfKWtFID2TSBjNjjBvEwqRbG2czOz3X_JfHfIgmXh6SDlFRXaXLOignsIP" + + discords_dispatcher = described_class.new(config) + + response = discords_dispatcher.dispatch(@payload) + + expect(response.code).to eq(401) + expect(response["message"]).to eq("Invalid Webhook Token") + end + end + end +end diff --git a/spec/bns/fetcher/birthday/notion_spec.rb b/spec/bns/fetcher/birthday/notion_spec.rb index b4eda8b..0824cd9 100644 --- a/spec/bns/fetcher/birthday/notion_spec.rb +++ b/spec/bns/fetcher/birthday/notion_spec.rb @@ -12,7 +12,7 @@ @fetcher = described_class.new(@config) end - describe "Arguments and methods" do + describe "attributes and arguments" do it { expect(@fetcher).to respond_to(:config) } it { expect(described_class).to respond_to(:new).with(1).arguments } @@ -77,8 +77,6 @@ it "fetch empty data from the given configured notion database" do VCR.use_cassette("notion_birthdays_with_empty_database") do - today = DateTime.now.strftime("%F").to_s - config = @config config[:database_id] = "w17e556d16c84272beb4ee73ab709639" @@ -90,17 +88,19 @@ end end - it "with provided database_id not matching any database" do + it "raises an exception caused by invalid database_id provided" do VCR.use_cassette("notion_birthdays_with_invalid_database_id") do config = @config config[:database_id] = "a17e556d16c84272beb4ee73ab709630" birthday_fetcher = described_class.new(@config) - expect { birthday_fetcher.fetch }.to raise_exception("Could not find database with ID: c17e556d-16c8-4272-beb4-ee73ab709631. Make sure the relevant pages and databases are shared with your integration.") + expect { + birthday_fetcher.fetch + }.to raise_exception("Could not find database with ID: c17e556d-16c8-4272-beb4-ee73ab709631. Make sure the relevant pages and databases are shared with your integration.") end end - it "with invalid or incorrect api_key provided" do + it "raise an exception caused by invalid or incorrect api_key provided" do VCR.use_cassette("notion_birthdays_with_invalid_api_key") do config = @config config[:secret] = "secret_ZELfDH6cf4Glc9NLPLxvsvdl9iZVD4qBCyMDXqch51C" diff --git a/spec/fixtures/vcr_cassettes/discord_failed_dispatch_invalid_webhook.yml b/spec/fixtures/vcr_cassettes/discord_failed_dispatch_invalid_webhook.yml new file mode 100644 index 0000000..17d3af0 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/discord_failed_dispatch_invalid_webhook.yml @@ -0,0 +1,76 @@ +--- +http_interactions: +- request: + method: post + uri: https://discord.com/api/webhooks/1196541734138691615/lFFCvFdMVEvfKWtFID2TSBjNjjBvEwqRbG2czOz3X_JfHfIgmXh6SDlFRXaXLOignsIP + body: + encoding: UTF-8 + string: '{"username":"Test Birthday Bot","avatar_url":"","content":"John Doe, + Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:"}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 401 + message: Unauthorized + headers: + Date: + - Thu, 18 Jan 2024 01:15:29 GMT + Content-Type: + - application/json + Content-Length: + - '51' + Connection: + - keep-alive + Set-Cookie: + - __cfruid=eb377df66f5657a4782fb8c14431d187beb69d44-1705540529; path=/; domain=.discord.com; + HttpOnly; Secure; SameSite=None + - __dcfduid=11b8f87ab59f11eeaab70267d10d6995; Expires=Tue, 16-Jan-2029 01:15:29 + GMT; Max-Age=157680000; Secure; HttpOnly; Path=/; SameSite=Lax + - __sdcfduid=11b8f87ab59f11eeaab70267d10d699593e467e102cbc97f5381c4732fed8ae7039487e1d319816ec03fd348e49e6cf7; + Expires=Tue, 16-Jan-2029 01:15:29 GMT; Max-Age=157680000; Secure; HttpOnly; + Path=/; SameSite=Lax + - _cfuvid=Npna0Krca1qlFBfxXs.hVoFdNiJqzezW.qVQs_VooXM-1705540529693-0-604800000; + path=/; domain=.discord.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Ratelimit-Bucket: + - 3d2712a9e4fe17cc9d3fed4a8e672e5f + X-Ratelimit-Limit: + - '5' + X-Ratelimit-Remaining: + - '4' + X-Ratelimit-Reset: + - '1705540531' + X-Ratelimit-Reset-After: + - '1' + Via: + - 1.1 google + Alt-Svc: + - h3=":443"; ma=86400 + Cf-Cache-Status: + - DYNAMIC + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=U2lLuCU4fShJhgkDXiRuGVg%2FswyPTOfAlreU92rdWOOhrE6etYpqYZlb%2B1U%2B5s2dWt20D%2BnO3QC0GHi1nXgVbKBt1sOesyqoPmorETLWGH0GcAmJXEDsGLhE8azk"}],"group":"cf-nel","max_age":604800}' + Nel: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - frame-ancestors 'none'; default-src 'none' + Server: + - cloudflare + Cf-Ray: + - 8472fcb63b55b3eb-MIA + body: + encoding: UTF-8 + string: '{"message": "Invalid Webhook Token", "code": 50027}' + recorded_at: Thu, 18 Jan 2024 01:15:29 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/discord_success_dispatch.yml b/spec/fixtures/vcr_cassettes/discord_success_dispatch.yml new file mode 100644 index 0000000..dd2aa65 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/discord_success_dispatch.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: post + uri: https://discord.com/api/webhooks/1196541734138691615/lFFCvFdMVEvfKWtFID2TSBjNjjBvEwqRbG2czOz3X_HfHfIgmXh6SDlFRXaXLOignsOj + body: + encoding: UTF-8 + string: '{"username":"Test Birthday Bot","avatar_url":"","content":"John Doe, + Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:"}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 204 + message: No Content + headers: + Date: + - Wed, 17 Jan 2024 19:21:19 GMT + Content-Type: + - text/html; charset=utf-8 + Connection: + - keep-alive + Set-Cookie: + - __cfruid=61cb2bc4ffef3eb7a521eb9c9f50efb6c5047148-1705519279; path=/; domain=.discord.com; + HttpOnly; Secure; SameSite=None + - __dcfduid=97d7b0a4b56d11eeafb20a5f3f9b9ba6; Expires=Mon, 15-Jan-2029 19:21:19 + GMT; Max-Age=157680000; Secure; HttpOnly; Path=/; SameSite=Lax + - __sdcfduid=97d7b0a4b56d11eeafb20a5f3f9b9ba622d1228c13eb8acf6358df84e202ae8dcb81b5ee7d02ab5b0ca1a4a5d6fd580c; + Expires=Mon, 15-Jan-2029 19:21:19 GMT; Max-Age=157680000; Secure; HttpOnly; + Path=/; SameSite=Lax + - _cfuvid=lefM3ID.6iEk48NeFoIafYAvQDMGOH_8NSC9Icr6q2g-1705519279871-0-604800000; + path=/; domain=.discord.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Ratelimit-Bucket: + - 3d2712a9e4fe17cc9d3fed4a8e672e5f + X-Ratelimit-Limit: + - '5' + X-Ratelimit-Remaining: + - '4' + X-Ratelimit-Reset: + - '1705519281' + X-Ratelimit-Reset-After: + - '1' + Via: + - 1.1 google + Alt-Svc: + - h3=":443"; ma=86400 + Cf-Cache-Status: + - DYNAMIC + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FPJUOsX0NFMbs5hMOtv59OJiEAGoTRZP95YCpEOhdHXNpYgr9c3VlwDI2g8VuTcDuc4NpaydwsVUlkUbKOJHvbH693ZJLxm1f79s9W1HUm9snTQAewBClkE5vDMy"}],"group":"cf-nel","max_age":604800}' + Nel: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - frame-ancestors 'none'; default-src 'none' + Server: + - cloudflare + Cf-Ray: + - 8470f5ea8d4a4c20-MIA + body: + encoding: UTF-8 + string: '' + recorded_at: Wed, 17 Jan 2024 19:21:19 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/discord_success_dispatch_empty_name.yml b/spec/fixtures/vcr_cassettes/discord_success_dispatch_empty_name.yml new file mode 100644 index 0000000..29685a1 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/discord_success_dispatch_empty_name.yml @@ -0,0 +1,74 @@ +--- +http_interactions: +- request: + method: post + uri: https://discord.com/api/webhooks/1196541734138691615/lFFCvFdMVEvfKWtFID2TSBjNjjBvEwqRbG2czOz3X_HfHfIgmXh6SDlFRXaXLOignsOj + body: + encoding: UTF-8 + string: '{"username":"Test Birthday Bot","avatar_url":"","content":"John Doe, + Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:"}' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 204 + message: No Content + headers: + Date: + - Thu, 18 Jan 2024 01:27:25 GMT + Content-Type: + - text/html; charset=utf-8 + Connection: + - keep-alive + Set-Cookie: + - __cfruid=f97a2ecfbe1fff672df8350618a87823c87a793d-1705541245; path=/; domain=.discord.com; + HttpOnly; Secure; SameSite=None + - __dcfduid=bc4224aab5a011eeb5eafab697dde37f; Expires=Tue, 16-Jan-2029 01:27:25 + GMT; Max-Age=157680000; Secure; HttpOnly; Path=/; SameSite=Lax + - __sdcfduid=bc4224aab5a011eeb5eafab697dde37fc6a43aa08d1b91bc8246140766b24d72077d42c4abde974c793ce7f24275a5df; + Expires=Tue, 16-Jan-2029 01:27:25 GMT; Max-Age=157680000; Secure; HttpOnly; + Path=/; SameSite=Lax + - _cfuvid=7XDYn6Kz6thJ8OwyBEAKVaq9NWYs2SuVKA7Y4saf1qE-1705541245299-0-604800000; + path=/; domain=.discord.com; HttpOnly; Secure; SameSite=None + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + X-Ratelimit-Bucket: + - 3d2712a9e4fe17cc9d3fed4a8e672e5f + X-Ratelimit-Limit: + - '5' + X-Ratelimit-Remaining: + - '4' + X-Ratelimit-Reset: + - '1705541246' + X-Ratelimit-Reset-After: + - '1' + Via: + - 1.1 google + Alt-Svc: + - h3=":443"; ma=86400 + Cf-Cache-Status: + - DYNAMIC + Report-To: + - '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=EFEd6FFRdPOx%2FhUltEevCO6MeZ05F%2BZX6hNyDFBPrhFGDWoEGlfGQANiH1X1PWVkscmG86iEralVujJiHlfD0XjSeepCqNGfvQVCP5l4TNnRSrUfYYWoMUUvtyrU"}],"group":"cf-nel","max_age":604800}' + Nel: + - '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}' + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - frame-ancestors 'none'; default-src 'none' + Server: + - cloudflare + Cf-Ray: + - 84730e2e3ced7430-MIA + body: + encoding: UTF-8 + string: '' + recorded_at: Thu, 18 Jan 2024 01:27:25 GMT +recorded_with: VCR 6.2.0 From ae90a6cdfc76a85e1fcba378b0b15dbbbc1cd081 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Thu, 18 Jan 2024 11:45:21 -0500 Subject: [PATCH 11/15] Apply improvements to some files --- lib/bns/fetcher/birthday/notion.rb | 13 ++++++------- lib/bns/formatter/birthday/discord.rb | 8 ++++++-- lib/bns/use_cases/use_case.rb | 10 +++++----- spec/bns/formatter/base_spec.rb | 2 -- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/bns/fetcher/birthday/notion.rb b/lib/bns/fetcher/birthday/notion.rb index 3d37326..4f454c0 100644 --- a/lib/bns/fetcher/birthday/notion.rb +++ b/lib/bns/fetcher/birthday/notion.rb @@ -22,7 +22,8 @@ def fetch end def normalize_response(response) - return [] if response == nil + return [] if response.nil? + normalized_response = [] response.map do |value| @@ -40,14 +41,12 @@ def normalize_response(response) private def validate_response(response) - error_codes = [401, 404] + error_codes = [401, 404] begin - if error_codes.include?(response["status"]) - raise response["message"] - else - return response - end + raise response["message"] if error_codes.include?(response["status"]) + + response rescue ArgumentError => e puts "Fetcher::Birthday::Notion Error: #{e.message}" end diff --git a/lib/bns/formatter/birthday/discord.rb b/lib/bns/formatter/birthday/discord.rb index 1b5f622..0f81222 100644 --- a/lib/bns/formatter/birthday/discord.rb +++ b/lib/bns/formatter/birthday/discord.rb @@ -9,14 +9,18 @@ class Discord include Base def format(data) + raise "Invalid data format" unless data.all? { |element| element.is_a?(Domain::Birthday) } + template = "NAME, Wishing you a very happy birthday! Enjoy your special day! :birthday: :gift:" payload = "" - data.each_index do |index| - payload += "#{template.gsub("NAME", data[index].individual_name)}\n" + data.each do |birthday| + payload += "#{template.gsub("NAME", birthday.individual_name)}\n" end payload + rescue ArgumentError => e + puts "Formatter::Birthday::Notion Error: #{e.message}" end end end diff --git a/lib/bns/use_cases/use_case.rb b/lib/bns/use_cases/use_case.rb index b6e29d4..0612c47 100644 --- a/lib/bns/use_cases/use_case.rb +++ b/lib/bns/use_cases/use_case.rb @@ -14,13 +14,13 @@ def initialize(options) def perform response = fetcher.fetch - if response.length > 0 - mappings = mapper.map(response) + return unless response.length.positive? - formatted_payload = formatter.format(mappings) + mappings = mapper.map(response) - dispatcher.dispatch(formatted_payload) - end + formatted_payload = formatter.format(mappings) + + dispatcher.dispatch(formatted_payload) end end end diff --git a/spec/bns/formatter/base_spec.rb b/spec/bns/formatter/base_spec.rb index 03e518b..8c3b15b 100644 --- a/spec/bns/formatter/base_spec.rb +++ b/spec/bns/formatter/base_spec.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true RSpec.describe Formatter::Base do - # it { is_expected.to respond_to(:format).with(1).arguments } - describe ".format" do let(:testing_class) { Class.new { include Formatter::Base } } From c61835686d2bdd01ab5f49e998b6502ced674448 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Thu, 18 Jan 2024 11:46:06 -0500 Subject: [PATCH 12/15] Add formatter and mapper tests for birthday use case --- spec/bns/formatter/birthday/discord_spec.rb | 26 +++++++++++++++++++++ spec/bns/mapper/birthday/notion_spec.rb | 23 ++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 spec/bns/formatter/birthday/discord_spec.rb create mode 100644 spec/bns/mapper/birthday/notion_spec.rb diff --git a/spec/bns/formatter/birthday/discord_spec.rb b/spec/bns/formatter/birthday/discord_spec.rb new file mode 100644 index 0000000..9c1f685 --- /dev/null +++ b/spec/bns/formatter/birthday/discord_spec.rb @@ -0,0 +1,26 @@ +RSpec.describe Formatter::Birthday::Discord do + before do + @data = [Domain::Birthday.new("Jane Doe", "2024-01-11"), Domain::Birthday.new("John Doe", "2024-01-18")] + @formatter = described_class.new + end + + describe "attributes and arguments" do + it { expect(described_class).to respond_to(:new).with(0).arguments } + it { expect(@formatter).to respond_to(:format).with(1).arguments } + end + + describe ".format" do + it "format the given data into a specific message" do + formatted_message = @formatter.format(@data) + + expect(formatted_message).to be_an_instance_of(String) + end + + it "format the given data into a specific message" do + invalid_data = [{ name: "John Doe", birth_date: "2024-01-18" }, + { name: "Jane Doe", birth_date: "2024-01-19" }] + + expect { @formatter.format(invalid_data) }.to raise_exception("Invalid data format") + end + end +end diff --git a/spec/bns/mapper/birthday/notion_spec.rb b/spec/bns/mapper/birthday/notion_spec.rb new file mode 100644 index 0000000..a0ba405 --- /dev/null +++ b/spec/bns/mapper/birthday/notion_spec.rb @@ -0,0 +1,23 @@ +RSpec.describe Mapper::Birthday::Notion do + before do + @data = [{ name: "Jane Doe", birth_date: "2024-01-11" }, { name: "Jhon Doe", birth_date: "2024-01-11" }] + @mapper = described_class.new + end + + describe "attributes and arguments" do + it { expect(described_class).to respond_to(:new).with(0).arguments } + it { expect(@mapper).to respond_to(:map).with(1).arguments } + end + + describe ".format" do + it "maps the given data into a domain specific one" do + mapped_data = @mapper.map(@data) + + are_birthdays = mapped_data.all? { |element| element.is_a?(Domain::Birthday) } + + expect(mapped_data).to be_an_instance_of(Array) + expect(mapped_data.length).to eq(2) + expect(are_birthdays).to be_truthy + end + end +end From 25c810999e602c6936ebbff28e83929047e2881c Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 23 Jan 2024 10:02:25 -0500 Subject: [PATCH 13/15] Remove test example on root file --- lib/bns.rb | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/lib/bns.rb b/lib/bns.rb index 00bc984..cc04912 100644 --- a/lib/bns.rb +++ b/lib/bns.rb @@ -6,35 +6,4 @@ module Bns include UseCases class Error < StandardError; end - - today = DateTime.now.strftime("%F").to_s - - options = { - fetch_options: { - base_url: "https://api.notion.com", - database_id: "NOTION_DATABASE_ID", - secret: "NOTION_SECRET", - filter: { - "filter": { - "or": [ - { - "property": "BD_this_year", - "date": { - "equals": today - } - } - ] - }, - "sorts": [] - } - }, - dispatch_options: { - webhook: "WEBHOOK_URL", - name: "BOT_NAME" - } - } - - use_case = UseCases.notify_birthday_from_notion_to_discord(options) - - use_case.perform end From fbaefa3865a01a66b624a872516b79cdfcc71ba0 Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 23 Jan 2024 11:23:56 -0500 Subject: [PATCH 14/15] Add rubocop autocorrections and avoided some rules to fail on CI --- .rubocop.yml | 7 +++++++ lib/bns/domain/response.rb | 0 lib/bns/mapper/birthday/postgres.rb | 0 lib/bns/use_cases/use_cases.rb | 3 ++- spec/bns/fetcher/birthday/notion_spec.rb | 7 +++++-- spec/bns/formatter/birthday/discord_spec.rb | 2 ++ spec/bns/mapper/birthday/notion_spec.rb | 2 ++ 7 files changed, 18 insertions(+), 3 deletions(-) delete mode 100644 lib/bns/domain/response.rb delete mode 100644 lib/bns/mapper/birthday/postgres.rb diff --git a/.rubocop.yml b/.rubocop.yml index e3462a7..e8a8b80 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,3 +11,10 @@ Style/StringLiteralsInInterpolation: Layout/LineLength: Max: 120 + Severity: info + +Style/Documentation: + Severity: info + +Metrics/BlockLength: + Severity: info diff --git a/lib/bns/domain/response.rb b/lib/bns/domain/response.rb deleted file mode 100644 index e69de29..0000000 diff --git a/lib/bns/mapper/birthday/postgres.rb b/lib/bns/mapper/birthday/postgres.rb deleted file mode 100644 index e69de29..0000000 diff --git a/lib/bns/use_cases/use_cases.rb b/lib/bns/use_cases/use_cases.rb index af112db..4c04a15 100644 --- a/lib/bns/use_cases/use_cases.rb +++ b/lib/bns/use_cases/use_cases.rb @@ -9,7 +9,8 @@ module UseCases def self.notify_birthday_from_notion_to_discord(options) options = { - fetcher: Fetcher::Birthday::Notion.new(options[:fetch_options]), # !TODO: Use a class for specific configs for fetcher and dispatcher, after everything is working + # !TODO: Use a class for specific configs for fetcher and dispatcher, after everything is working + fetcher: Fetcher::Birthday::Notion.new(options[:fetch_options]), mapper: Mapper::Birthday::Notion.new, formatter: Formatter::Birthday::Discord.new, dispatcher: Dispatcher::Discord.new(options[:dispatch_options]) diff --git a/spec/bns/fetcher/birthday/notion_spec.rb b/spec/bns/fetcher/birthday/notion_spec.rb index 0824cd9..69a7c20 100644 --- a/spec/bns/fetcher/birthday/notion_spec.rb +++ b/spec/bns/fetcher/birthday/notion_spec.rb @@ -94,9 +94,12 @@ config[:database_id] = "a17e556d16c84272beb4ee73ab709630" birthday_fetcher = described_class.new(@config) - expect { + expected_exception = "Could not find database with ID: c17e556d-16c8-4272-beb4-ee73ab709631. " \ + "Make sure the relevant pages and databases are shared with your integration." + + expect do birthday_fetcher.fetch - }.to raise_exception("Could not find database with ID: c17e556d-16c8-4272-beb4-ee73ab709631. Make sure the relevant pages and databases are shared with your integration.") + end.to raise_exception(expected_exception) end end diff --git a/spec/bns/formatter/birthday/discord_spec.rb b/spec/bns/formatter/birthday/discord_spec.rb index 9c1f685..b93937e 100644 --- a/spec/bns/formatter/birthday/discord_spec.rb +++ b/spec/bns/formatter/birthday/discord_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.describe Formatter::Birthday::Discord do before do @data = [Domain::Birthday.new("Jane Doe", "2024-01-11"), Domain::Birthday.new("John Doe", "2024-01-18")] diff --git a/spec/bns/mapper/birthday/notion_spec.rb b/spec/bns/mapper/birthday/notion_spec.rb index a0ba405..8cc292c 100644 --- a/spec/bns/mapper/birthday/notion_spec.rb +++ b/spec/bns/mapper/birthday/notion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.describe Mapper::Birthday::Notion do before do @data = [{ name: "Jane Doe", birth_date: "2024-01-11" }, { name: "Jhon Doe", birth_date: "2024-01-11" }] From 885664617bb547d1924cbd29dc0cb5c45601408e Mon Sep 17 00:00:00 2001 From: Lorenzo Zuluaga Date: Tue, 23 Jan 2024 11:28:10 -0500 Subject: [PATCH 15/15] Add missing permissions on CI file --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c49f774..b2a96ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,4 +24,4 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Run the default task - run: bundle exec rake + run: chmod +x bin/console && bundle exec rake