From a5a4b15e5b245e3d2630df20b0133465674e778f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 18 Jun 2025 19:03:11 +0530 Subject: [PATCH] feat: add notion API --- lib/notion.rb | 54 +++++++++++++++++++++ spec/lib/notion_spec.rb | 102 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 lib/notion.rb create mode 100644 spec/lib/notion_spec.rb diff --git a/lib/notion.rb b/lib/notion.rb new file mode 100644 index 000000000..486c97676 --- /dev/null +++ b/lib/notion.rb @@ -0,0 +1,54 @@ +class Notion + BASE_URL = 'https://api.notion.com/v1'.freeze + + def initialize(access_token) + @access_token = access_token + raise ArgumentError, 'Missing Credentials' if access_token.blank? + end + + def search(query = '', sort = nil) + payload = { query: query } + payload[:sort] = sort if sort.present? + + response = post('search', payload) + process_response(response) + end + + def page(page_id) + raise ArgumentError, 'Missing page id' if page_id.blank? + + response = get("pages/#{page_id}") + process_response(response) + end + + private + + def get(path) + HTTParty.get( + "#{BASE_URL}/#{path}", + headers: default_headers + ) + end + + def post(path, payload) + HTTParty.post( + "#{BASE_URL}/#{path}", + headers: default_headers, + body: payload.to_json + ) + end + + def default_headers + { + 'Authorization' => "Bearer #{@access_token}", + 'Notion-Version' => GlobalConfigService.load('NOTION_VERSION', '2022-06-28'), + 'Content-Type' => 'application/json' + } + end + + def process_response(response) + return response.parsed_response.with_indifferent_access if response.success? + + { error: response.parsed_response, error_code: response.code } + end +end \ No newline at end of file diff --git a/spec/lib/notion_spec.rb b/spec/lib/notion_spec.rb new file mode 100644 index 000000000..b487b3499 --- /dev/null +++ b/spec/lib/notion_spec.rb @@ -0,0 +1,102 @@ +require 'rails_helper' + +RSpec.describe Notion do + let(:access_token) { 'notion_access_token' } + let(:notion_client) { described_class.new(access_token) } + + describe '#initialize' do + it 'initializes with access token' do + expect(notion_client.instance_variable_get(:@access_token)).to eq(access_token) + end + + it 'raises error when access token is blank' do + expect { described_class.new('') }.to raise_error(ArgumentError, 'Missing Credentials') + end + end + + describe '#search' do + let(:query) { 'test query' } + let(:sort) { { direction: 'ascending', timestamp: 'last_edited_time' } } + let(:response_body) { { 'results' => [{ 'id' => '123', 'object' => 'page' }] } } + let(:response) { instance_double(HTTParty::Response, success?: true, parsed_response: response_body) } + + before do + allow(HTTParty).to receive(:post).and_return(response) + end + + it 'makes a POST request to search endpoint' do + notion_client.search(query) + + expect(HTTParty).to have_received(:post).with( + 'https://api.notion.com/v1/search', + headers: { + 'Authorization' => "Bearer #{access_token}", + 'Notion-Version' => '2022-06-28', + 'Content-Type' => 'application/json' + }, + body: { query: query }.to_json + ) + end + + it 'includes sort parameters when provided' do + notion_client.search(query, sort) + + expect(HTTParty).to have_received(:post).with( + 'https://api.notion.com/v1/search', + headers: anything, + body: { query: query, sort: sort }.to_json + ) + end + + it 'returns parsed response on success' do + result = notion_client.search(query) + expect(result).to eq(response_body.with_indifferent_access) + end + + context 'when request fails' do + let(:error_response) { { 'object' => 'error', 'status' => 400 } } + let(:failed_response) { instance_double(HTTParty::Response, success?: false, parsed_response: error_response, code: 400) } + + before do + allow(HTTParty).to receive(:post).and_return(failed_response) + end + + it 'returns error response' do + result = notion_client.search(query) + expect(result).to eq({ error: error_response, error_code: 400 }) + end + end + end + + describe '#page' do + let(:page_id) { 'page-123' } + let(:response_body) { { 'id' => page_id, 'object' => 'page' } } + let(:response) { instance_double(HTTParty::Response, success?: true, parsed_response: response_body) } + + before do + allow(HTTParty).to receive(:get).and_return(response) + end + + it 'makes a GET request to pages endpoint' do + notion_client.page(page_id) + + expect(HTTParty).to have_received(:get).with( + "https://api.notion.com/v1/pages/#{page_id}", + headers: { + 'Authorization' => "Bearer #{access_token}", + 'Notion-Version' => '2022-06-28', + 'Content-Type' => 'application/json' + } + ) + end + + it 'returns parsed response on success' do + result = notion_client.page(page_id) + expect(result).to eq(response_body.with_indifferent_access) + end + + it 'raises error when page_id is blank' do + expect { notion_client.page('') }.to raise_error(ArgumentError, 'Missing page id') + end + end +end \ No newline at end of file