diff --git a/Gemfile.lock b/Gemfile.lock index 37fd9fd..10bcf2a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - intelligent_foods (0.0.0) + batchline-solutions-core (0.0.0) GEM remote: https://rubygems.org/ @@ -76,8 +76,8 @@ PLATFORMS arm64-darwin-21 DEPENDENCIES + batchline_solutions! factory_bot - intelligent_foods! json-schema pry (~> 0.14) rake (~> 13.0) diff --git a/README.md b/README.md index 27a8490..a4c2f18 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# IntelligentFoods +# BatchlineSolutions ## Installation @@ -18,7 +18,7 @@ executing: ``` require "intelligent-foods-ruby" -IntelligentFoods.configure do |config| +BatchlineSolutions.configure do |config| config.client_id = "XXXXXX" config.client_secret = "YYYYYY" config.environment = "preview" @@ -28,7 +28,7 @@ end ### Authentication ``` -@client = IntelligentFoods.client +@client = BatchlineSolutions.client @client.authenticate! ``` @@ -52,7 +52,7 @@ For `rspec`, add the following line to your `spec/rails_helper.rb` or `spec/spec_helper` if `rails_helper` does not exist: ``` -require "intelligent_foods/rspec" +require "core/rspec" ``` ## Contributing @@ -68,5 +68,5 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Code of Conduct -Everyone interacting in the IntelligentFoods project's codebases, issue +Everyone interacting in the BatchlineSolutions project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/intelligent-foods-ruby/blob/main/CODE_OF_CONDUCT.md). diff --git a/bin/console b/bin/console index 4eb347d..3415996 100755 --- a/bin/console +++ b/bin/console @@ -2,7 +2,7 @@ # frozen_string_literal: true require "bundler/setup" -require "intelligent_foods" +require "core" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/intelligent-foods-ruby.gemspec b/intelligent-foods-ruby.gemspec index 6181529..a8a07b6 100644 --- a/intelligent-foods-ruby.gemspec +++ b/intelligent-foods-ruby.gemspec @@ -1,23 +1,24 @@ # frozen_string_literal: true -require_relative "lib/intelligent_foods/version" +require_relative "lib/core/version" Gem::Specification.new do |spec| - spec.name = "intelligent_foods" - spec.version = IntelligentFoods::VERSION - spec.authors = ["Chris Woodford"] - spec.email = ["chris@gobble.com"] - - spec.summary = "Ruby SDK for the Intelligent Foods API" - spec.homepage = "https://intelligentfoods.com" + spec.name = "batchline-solutions-core" + spec.version = Bls::Core::VERSION + spec.authors = ["Chris Woodford", "Rohan Likhite"] + spec.email = ["chris.woodford@batchline.solutions", + "rohan.likhite@batchline.solutions"] + + spec.summary = "Ruby SDK for the Batchline Solutions Core API" + spec.homepage = "https://batchline.solutions.com" spec.license = "MIT" spec.required_ruby_version = ">= 3.1" spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "https://github.com/Intelligent-Foods/intelligent-foods-ruby" - spec.metadata["changelog_uri"] = "https://github.com/Intelligent-Foods/intelligent-foods-rub/CHANGELOG.mdy" + spec.metadata["source_code_uri"] = "https://github.com/batchlinesolutions/core-ruby" + spec.metadata["changelog_uri"] = "https://github.com/batchlinesolutions/core-ruby//CHANGELOG.mdy" # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. diff --git a/lib/core.rb b/lib/core.rb new file mode 100644 index 0000000..c88018e --- /dev/null +++ b/lib/core.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "base64" +require "json" +require "net/http" +require "ostruct" + +require "core/api_client" +require "core/api/v1" +require "core/api/v1/authenticator" +require "core/api_operations/retrieve" +require "core/authorization/base" +require "core/authorization/basic" +require "core/authorization/blank" +require "core/resources/api_error" +require "core/authorization/bearer" +require "core/resources/object" +require "core/resources/inventory_level" +require "core/resources/order" +require "core/resources/order_item" +require "core/serializers/order_item_serializer" +require "core/resources/menu" +require "core/resources/menu_item" +require "core/serializers/menu_item_serializer" +require "core/resources/recipient" +require "core/serializers/recipient_serializer" +require "core/version" +require "core/errors" + +module Bls + module Core + class Error < StandardError; end + + class << self + attr_accessor :client_id, :client_secret, :environment + + def configure + yield self + end + end + end +end diff --git a/lib/core/api/v1.rb b/lib/core/api/v1.rb new file mode 100644 index 0000000..2ac36f2 --- /dev/null +++ b/lib/core/api/v1.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Bls + module Core + class V1 + attr_accessor :authentication + attr_reader :environment, :username, :password + + PRODUCTION = "production" + STAGING = "staging" + + def initialize(username: nil, password: nil, authentication: nil, + environment: STAGING) + @authentication = authentication + @environment = environment + @username = username + @password = password + end + + def base_url + "https://api.sunbasket.#{tld}/partner/v1" + end + + def authenticate! + return if authenticated? + + authenticator = V1::Authenticator.build(self) + @authentication = authenticator.save! + end + + def client + build_api_client! + end + + def authenticated? + authentication.present? + end + + def reset_authentication + @authentication = nil + self + end + + def self.build(config: Bls::Core) + new(environment: config.environment, + username: config.client_id, + password: config.client_secret) + end + + protected + + def build_api_client! + authenticate! + Bls::Core::ApiClient.build(self) + end + + def tld + @tld ||= determine_tld + end + + def determine_tld + if environment == PRODUCTION + "com" + else + "dev" + end + end + end + end +end diff --git a/lib/core/api/v1/authenticator.rb b/lib/core/api/v1/authenticator.rb new file mode 100644 index 0000000..77c9e17 --- /dev/null +++ b/lib/core/api/v1/authenticator.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Bls + module Core + class V1::Authenticator + attr_accessor :base_url, :client_id, :client_secret + + def initialize(base_url:, client_id:, client_secret:) + @base_url = base_url + @client_id = client_id + @client_secret = client_secret + end + + def save! + path = "#{base_url}/token" + body = { client_id: client_id, client_secret: client_secret } + auth = Authorization::Basic. + factory(client_id: client_id, client_secret: client_secret) + client = Core::ApiClient.new(authentication: auth) + response = client.post(path: path, body: body) + handle_authentication_response!(response: response.data) + end + + def self.build(api) + new(base_url: api.base_url, + client_id: api.username, + client_secret: api.password) + end + + protected + + def handle_authentication_response!(response:) + if response_has_errors?(response) + handle_errors! + else + handle_successful_authentication_response(response) + end + end + + def response_has_errors?(response) + response.has_key?(:error) + end + + def handle_successful_authentication_response(response) + access_token = response[:access_token] + Core::Authorization::Bearer.new(token: access_token) + end + + def handle_errors! + raise AuthenticationError.new(status: 401, title: "Authentication Failed") + end + end + end +end diff --git a/lib/core/api_client.rb b/lib/core/api_client.rb new file mode 100644 index 0000000..6a7ad9b --- /dev/null +++ b/lib/core/api_client.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module Bls + module Core + class ApiClient + attr_reader :api, :authentication + + def initialize(api: nil, authentication: nil) + @api = api + @authentication = authentication || Authorization::Blank.new + end + + def post(path:, body:) + uri = URI(path) + request = build_request_with_body(uri: uri, body: body) + execute_request(request: request, uri: request.uri) + end + + def delete(path:, **) + uri = URI(path) + request = Net::HTTP::Delete.new(uri) + execute_request(request: request, uri: request.uri) + end + + def get(path:, **) + uri = URI(path) + request = Net::HTTP::Get.new(uri) + execute_request(request: request, uri: request.uri) + end + + def execute_request(request:, uri:) + Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| + request["Authorization"] = authentication.header + response = http.request(request) + handle_response(response: response) + end + end + + def self.build(api) + new(api: api, authentication: api.authentication) + end + + protected + + def build_request_with_body(uri:, body:) + request = Net::HTTP::Post.new(uri) + request.body = body.to_json + request["content-type"] = "application/json" + request + end + + def handle_response(response:) + if authentication_failed?(response.code) + handle_authentication_error! + else + body = parse_response_body(response) + OpenStruct.new(data: body, success?: request_successful?(response.code)) + end + end + + def handle_authentication_error! + api.reset_authentication + raise AuthenticationError.new(status: 401, + title: "Authentication Failed") + end + + def parse_response_body(response) + return {} if empty_response?(response.code) + return {} if redirection?(response.code) + + JSON.parse(response.body, symbolize_names: true) + end + + def empty_response?(response_code) + response_code.to_i == 204 + end + + def redirection?(response_code) + response_code.to_i.between?(300, 399) + end + + def request_successful?(response_code) + response_code.to_i.between?(200, 299) + end + + def authentication_failed?(response_code) + response_code.to_i == 401 + end + end + end +end diff --git a/lib/core/api_operations/retrieve.rb b/lib/core/api_operations/retrieve.rb new file mode 100644 index 0000000..0862dac --- /dev/null +++ b/lib/core/api_operations/retrieve.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Bls + module Core + module ApiOperations + module Retrieve + def retrieve(id) + resource = new(id: id) + api_client = resource.client + response = api_client.get(path: resource.resource_path) + if response.success? + build_from_response(response.data) + else + raise ResourceRetrievalError.build(response) + end + end + + def retrieve_all + resource = new + api_client = resource.client + response = api_client.get(path: resource.resources_path) + if response.success? + response.data.map { |id| new(id: id) } + else + raise ResourceRetrievalError.build(response) + end + end + end + end + end +end diff --git a/lib/core/authorization/base.rb b/lib/core/authorization/base.rb new file mode 100644 index 0000000..52630a6 --- /dev/null +++ b/lib/core/authorization/base.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Bls + module Core + module Authorization + class Base + attr_reader :token + + def initialize(token: nil) + @token = token + end + end + end + end +end diff --git a/lib/core/authorization/basic.rb b/lib/core/authorization/basic.rb new file mode 100644 index 0000000..010c8ea --- /dev/null +++ b/lib/core/authorization/basic.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Bls + module Core + module Authorization + class Basic < Base + def header + "Basic #{token}" + end + + def self.factory(client_id:, client_secret:) + encoded_token = Base64.strict_encode64("#{client_id}:#{client_secret}") + new(token: encoded_token) + end + end + end + end +end diff --git a/lib/core/authorization/bearer.rb b/lib/core/authorization/bearer.rb new file mode 100644 index 0000000..81f960d --- /dev/null +++ b/lib/core/authorization/bearer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Bls + module Core + module Authorization + class Bearer < Base + def header + "Bearer #{token}" + end + end + end + end +end diff --git a/lib/core/authorization/blank.rb b/lib/core/authorization/blank.rb new file mode 100644 index 0000000..951c81b --- /dev/null +++ b/lib/core/authorization/blank.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Bls + module Core + module Authorization + class Blank < Base + def header; end + end + end + end +end diff --git a/lib/core/errors.rb b/lib/core/errors.rb new file mode 100644 index 0000000..eeb7f03 --- /dev/null +++ b/lib/core/errors.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Bls + module Core + class ResourceRetrievalError < ApiError; end + + class OrderNotCancelledError < ApiError; end + + class OrderNotCreatedError < ApiError; end + + class AuthenticationError < ApiError; end + end +end diff --git a/lib/core/resources/api_error.rb b/lib/core/resources/api_error.rb new file mode 100644 index 0000000..1a7475f --- /dev/null +++ b/lib/core/resources/api_error.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Bls + module Core + class ApiError < StandardError + attr_reader :status, :title, :details + + def initialize(status:, title:, details: nil) + @status = status + @title = title + @details = details + super + end + + def message + if details.blank? + "#{status} - #{title}" + else + "#{status} #{title} - #{description}" + end + end + + def self.build(response) + data = response.data + data => { status:, title:, details: } + new(status: status, title: title, details: details) + end + + protected + + def description + details[:description] + end + end + end +end diff --git a/lib/core/resources/inventory_level.rb b/lib/core/resources/inventory_level.rb new file mode 100644 index 0000000..29195f5 --- /dev/null +++ b/lib/core/resources/inventory_level.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Bls + module Core + class InventoryLevel < Core::Object + def object_name + "inventory-level" + end + + def resources_path + "#{api.base_url}/products/inventory-levels" + end + + def self.retrieve_all + resource = new + api_client = resource.client + response = api_client.get(path: resource.resources_path) + if response.success? + response.data[:products].map { |product| build(product) } + else + raise ResourceRetrievalError.build(response) + end + end + end + end +end diff --git a/lib/core/resources/menu.rb b/lib/core/resources/menu.rb new file mode 100644 index 0000000..6aba288 --- /dev/null +++ b/lib/core/resources/menu.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Bls + module Core + class Menu < Core::Object + def initialize(args = {}) + super + end + + def object_name + "menu" + end + + def resources_path + "#{api.base_url}/menus" + end + + def self.build_from_response(data) + menu = build(data) + menu.items = MenuItem.build(data[:items]) + menu + end + end + end +end diff --git a/lib/core/resources/menu_item.rb b/lib/core/resources/menu_item.rb new file mode 100644 index 0000000..c9894a8 --- /dev/null +++ b/lib/core/resources/menu_item.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Bls + module Core + class MenuItem < Core::Object + def initialize(args = {}) + super + end + end + end +end diff --git a/lib/core/resources/object.rb b/lib/core/resources/object.rb new file mode 100644 index 0000000..edead66 --- /dev/null +++ b/lib/core/resources/object.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Bls + module Core + class Object < OpenStruct + extend Bls::Core::ApiOperations::Retrieve + + def self.build(data) + JSON.parse(data.to_json, object_class: self) + end + + def resources_path + "#{api.base_url}/#{object_name.downcase}" + end + + def resource_path + "#{api.base_url}/#{object_name.downcase}/#{id}" + end + + def client + api.client + end + + def api + @api ||= Bls::Core::V1.build + end + end + end +end diff --git a/lib/core/resources/order.rb b/lib/core/resources/order.rb new file mode 100644 index 0000000..2d74ba1 --- /dev/null +++ b/lib/core/resources/order.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +module Bls + module Core + class Order < Core::Object + ACCEPTED = "accepted" + CANCELLED = "cancelled" + ERROR = "error" + IN_PROCESS = "in_process" + INITIALIZED = "initialized" + PROCESSED = "processed" + + attr_reader :skip_temperature_check, :skip_address_check + + def initialize(skip_temperature_check: false, skip_address_check: false, **) + super + @skip_temperature_check = skip_temperature_check + @skip_address_check = skip_address_check + end + + def self.build_from_response(data) + order = build(data) + order[:items] = OrderItem.build(data[:items]) + order[:ship_to] = Recipient.build(data[:ship_to]) + order + end + + def object_name + "order" + end + + def create! + response = client.post(path: resources_path, body: request_body) + if response.success? + Order::build_from_response(response.data) + else + handle_order_not_created(response) + end + end + + def cancel! + response = client.delete(path: resource_path) + if response.success? + mark_as_cancelled + self + else + handle_order_not_cancelled(response) + end + end + + def request_body + @request_body ||= { + menu_id: menu.id, + reference_id: external_id.to_s, + ship_to: ship_to, + delivery_date: delivery_date, + items: items_json, + validation_options: validation_options, + callback_url: callback_url, + callback_headers: callback_headers, + } + end + + def cancelled? + status.downcase == CANCELLED + end + + def valid? + status.downcase != ERROR + end + + def accepted? + status.downcase == ACCEPTED + end + + protected + + def handle_order_not_created(response) + mark_as_invalid + raise OrderNotCreatedError.build(response) + end + + def handle_order_not_cancelled(response) + mark_as_invalid + raise OrderNotCancelledError.build(response) + end + + def mark_as_cancelled + self[:status] = CANCELLED + end + + def mark_as_invalid + self[:status] = ERROR + end + + def items_json + return if items.nil? + + items.map do |item| + OrderItemSerializer.new(item).to_json + end + end + + def ship_to + RecipientSerializer.new(recipient).to_json + end + + def validation_options + { + skip_temperature_check: skip_temperature_check, + skip_address_check: skip_address_check, + } + end + end + end +end diff --git a/lib/core/resources/order_item.rb b/lib/core/resources/order_item.rb new file mode 100644 index 0000000..5d147a2 --- /dev/null +++ b/lib/core/resources/order_item.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Bls + module Core + class OrderItem < Core::Object + def initialize(args = {}) + super + end + end + end +end diff --git a/lib/core/resources/recipient.rb b/lib/core/resources/recipient.rb new file mode 100644 index 0000000..baeb935 --- /dev/null +++ b/lib/core/resources/recipient.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Bls + module Core + class Recipient < Core::Object + def initialize(args = {}) + super + end + end + end +end diff --git a/lib/core/rspec.rb b/lib/core/rspec.rb new file mode 100644 index 0000000..cd86c96 --- /dev/null +++ b/lib/core/rspec.rb @@ -0,0 +1,9 @@ +require "rspec/rails" +require "core/testing/fake" +require "core/testing/fake/callback" + +RSpec.configure do |config| + config.before(:each) do + stub_const("Core", Core::Testing::Fake) + end +end diff --git a/lib/intelligent_foods/serializers/menu_item_serializer.rb b/lib/core/serializers/menu_item_serializer.rb similarity index 88% rename from lib/intelligent_foods/serializers/menu_item_serializer.rb rename to lib/core/serializers/menu_item_serializer.rb index f31c06b..832723a 100644 --- a/lib/intelligent_foods/serializers/menu_item_serializer.rb +++ b/lib/core/serializers/menu_item_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module IntelligentFoods +module Core class MenuItemSerializer < SimpleDelegator def to_json { diff --git a/lib/core/serializers/order_item_serializer.rb b/lib/core/serializers/order_item_serializer.rb new file mode 100644 index 0000000..768f053 --- /dev/null +++ b/lib/core/serializers/order_item_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Bls + module Core + class OrderItemSerializer < SimpleDelegator + def to_json + { + sku: sku, + protein_sku: protein_sku, + quantity: quantity, + } + end + end + end +end diff --git a/lib/core/serializers/recipient_serializer.rb b/lib/core/serializers/recipient_serializer.rb new file mode 100644 index 0000000..3a1a02f --- /dev/null +++ b/lib/core/serializers/recipient_serializer.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Bls + module Core + class RecipientSerializer < SimpleDelegator + def to_json + recipient_object = { + name: name, + street1: street1, + street2: street2, + city: city, + state: state, + zip: zip, + zip4: zip4, + email: email, + phone: phone, + delivery_instructions: delivery_instructions, + } + remove_blank_values(recipient_object) + end + + protected + + def remove_blank_values(obj) + obj.delete_if do |_k, v| + v.blank? + end + end + end + end +end diff --git a/lib/intelligent_foods/testing/fake.rb b/lib/core/testing/fake.rb similarity index 99% rename from lib/intelligent_foods/testing/fake.rb rename to lib/core/testing/fake.rb index 64dcda8..d33e9d5 100644 --- a/lib/intelligent_foods/testing/fake.rb +++ b/lib/core/testing/fake.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module IntelligentFoods +module Core module Testing class Fake def self.configure(*); end diff --git a/lib/intelligent_foods/testing/fake/callback.rb b/lib/core/testing/fake/callback.rb similarity index 99% rename from lib/intelligent_foods/testing/fake/callback.rb rename to lib/core/testing/fake/callback.rb index 0beafd1..e75462e 100644 --- a/lib/intelligent_foods/testing/fake/callback.rb +++ b/lib/core/testing/fake/callback.rb @@ -1,4 +1,4 @@ -module IntelligentFoods +module Core module Testing class Fake::Callback def self.shipment_created(order_id: SecureRandom.uuid, diff --git a/lib/core/version.rb b/lib/core/version.rb new file mode 100644 index 0000000..deae573 --- /dev/null +++ b/lib/core/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Bls + module Core + VERSION = "0.0.0" + end +end diff --git a/lib/intelligent_foods.rb b/lib/intelligent_foods.rb deleted file mode 100644 index a2a9d78..0000000 --- a/lib/intelligent_foods.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "base64" -require "json" -require "net/http" -require "ostruct" - -require "intelligent_foods/api_client" -require "intelligent_foods/api/v1" -require "intelligent_foods/api/v1/authenticator" -require "intelligent_foods/api_operations/retrieve" -require "intelligent_foods/authorization/base" -require "intelligent_foods/authorization/basic" -require "intelligent_foods/authorization/blank" -require "intelligent_foods/resources/api_error" -require "intelligent_foods/authorization/bearer" -require "intelligent_foods/resources/object" -require "intelligent_foods/resources/inventory_level" -require "intelligent_foods/resources/order" -require "intelligent_foods/resources/order_item" -require "intelligent_foods/serializers/order_item_serializer" -require "intelligent_foods/resources/menu" -require "intelligent_foods/resources/menu_item" -require "intelligent_foods/serializers/menu_item_serializer" -require "intelligent_foods/resources/recipient" -require "intelligent_foods/serializers/recipient_serializer" -require "intelligent_foods/version" -require "intelligent_foods/errors" - -module IntelligentFoods - class Error < StandardError; end - - class << self - attr_accessor :client_id, :client_secret, :environment - - def configure - yield self - end - end -end diff --git a/lib/intelligent_foods/api/v1.rb b/lib/intelligent_foods/api/v1.rb deleted file mode 100644 index 095fe2e..0000000 --- a/lib/intelligent_foods/api/v1.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class V1 - attr_accessor :authentication - attr_reader :environment, :username, :password - - PRODUCTION = "production" - STAGING = "staging" - - def initialize(username: nil, password: nil, authentication: nil, - environment: STAGING) - @authentication = authentication - @environment = environment - @username = username - @password = password - end - - def base_url - "https://api.sunbasket.#{tld}/partner/v1" - end - - def authenticate! - return if authenticated? - - authenticator = V1::Authenticator.build(self) - @authentication = authenticator.save! - end - - def client - build_api_client! - end - - def authenticated? - authentication.present? - end - - def reset_authentication - @authentication = nil - self - end - - def self.build(config: IntelligentFoods) - new(environment: config.environment, - username: config.client_id, - password: config.client_secret) - end - - protected - - def build_api_client! - authenticate! - IntelligentFoods::ApiClient.build(self) - end - - def tld - @tld ||= determine_tld - end - - def determine_tld - if environment == PRODUCTION - "com" - else - "dev" - end - end - end -end diff --git a/lib/intelligent_foods/api/v1/authenticator.rb b/lib/intelligent_foods/api/v1/authenticator.rb deleted file mode 100644 index d7e6bd1..0000000 --- a/lib/intelligent_foods/api/v1/authenticator.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class V1::Authenticator - attr_accessor :base_url, :client_id, :client_secret - - def initialize(base_url:, client_id:, client_secret:) - @base_url = base_url - @client_id = client_id - @client_secret = client_secret - end - - def save! - path = "#{base_url}/token" - body = { client_id: client_id, client_secret: client_secret } - auth = Authorization::Basic. - factory(client_id: client_id, client_secret: client_secret) - client = IntelligentFoods::ApiClient.new(authentication: auth) - response = client.post(path: path, body: body) - handle_authentication_response!(response: response.data) - end - - def self.build(api) - new(base_url: api.base_url, - client_id: api.username, - client_secret: api.password) - end - - protected - - def handle_authentication_response!(response:) - if response_has_errors?(response) - handle_errors! - else - handle_successful_authentication_response(response) - end - end - - def response_has_errors?(response) - response.has_key?(:error) - end - - def handle_successful_authentication_response(response) - access_token = response[:access_token] - IntelligentFoods::Authorization::Bearer.new(token: access_token) - end - - def handle_errors! - raise AuthenticationError.new(status: 401, title: "Authentication Failed") - end - end -end diff --git a/lib/intelligent_foods/api_client.rb b/lib/intelligent_foods/api_client.rb deleted file mode 100644 index d18b094..0000000 --- a/lib/intelligent_foods/api_client.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class ApiClient - attr_reader :api, :authentication - - def initialize(api: nil, authentication: nil) - @api = api - @authentication = authentication || Authorization::Blank.new - end - - def post(path:, body:) - uri = URI(path) - request = build_request_with_body(uri: uri, body: body) - execute_request(request: request, uri: request.uri) - end - - def delete(path:, **) - uri = URI(path) - request = Net::HTTP::Delete.new(uri) - execute_request(request: request, uri: request.uri) - end - - def get(path:, **) - uri = URI(path) - request = Net::HTTP::Get.new(uri) - execute_request(request: request, uri: request.uri) - end - - def execute_request(request:, uri:) - Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| - request["Authorization"] = authentication.header - response = http.request(request) - handle_response(response: response) - end - end - - def self.build(api) - new(api: api, authentication: api.authentication) - end - - protected - - def build_request_with_body(uri:, body:) - request = Net::HTTP::Post.new(uri) - request.body = body.to_json - request["content-type"] = "application/json" - request - end - - def handle_response(response:) - if authentication_failed?(response.code) - handle_authentication_error! - else - body = parse_response_body(response) - OpenStruct.new(data: body, success?: request_successful?(response.code)) - end - end - - def handle_authentication_error! - api.reset_authentication - raise AuthenticationError.new(status: 401, - title: "Authentication Failed") - end - - def parse_response_body(response) - return {} if empty_response?(response.code) - return {} if redirection?(response.code) - - JSON.parse(response.body, symbolize_names: true) - end - - def empty_response?(response_code) - response_code.to_i == 204 - end - - def redirection?(response_code) - response_code.to_i.between?(300, 399) - end - - def request_successful?(response_code) - response_code.to_i.between?(200, 299) - end - - def authentication_failed?(response_code) - response_code.to_i == 401 - end - end -end diff --git a/lib/intelligent_foods/api_operations/retrieve.rb b/lib/intelligent_foods/api_operations/retrieve.rb deleted file mode 100644 index 0a8e7e3..0000000 --- a/lib/intelligent_foods/api_operations/retrieve.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - module ApiOperations - module Retrieve - def retrieve(id) - resource = new(id: id) - api_client = resource.client - response = api_client.get(path: resource.resource_path) - if response.success? - build_from_response(response.data) - else - raise ResourceRetrievalError.build(response) - end - end - - def retrieve_all - resource = new - api_client = resource.client - response = api_client.get(path: resource.resources_path) - if response.success? - response.data.map { |id| new(id: id) } - else - raise ResourceRetrievalError.build(response) - end - end - end - end -end diff --git a/lib/intelligent_foods/authorization/base.rb b/lib/intelligent_foods/authorization/base.rb deleted file mode 100644 index 6d45ba3..0000000 --- a/lib/intelligent_foods/authorization/base.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - module Authorization - class Base - attr_reader :token - - def initialize(token: nil) - @token = token - end - end - end -end diff --git a/lib/intelligent_foods/authorization/basic.rb b/lib/intelligent_foods/authorization/basic.rb deleted file mode 100644 index 443618d..0000000 --- a/lib/intelligent_foods/authorization/basic.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - module Authorization - class Basic < Base - def header - "Basic #{token}" - end - - def self.factory(client_id:, client_secret:) - encoded_token = Base64.strict_encode64("#{client_id}:#{client_secret}") - new(token: encoded_token) - end - end - end -end diff --git a/lib/intelligent_foods/authorization/bearer.rb b/lib/intelligent_foods/authorization/bearer.rb deleted file mode 100644 index 034ed3d..0000000 --- a/lib/intelligent_foods/authorization/bearer.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - module Authorization - class Bearer < Base - def header - "Bearer #{token}" - end - end - end -end diff --git a/lib/intelligent_foods/authorization/blank.rb b/lib/intelligent_foods/authorization/blank.rb deleted file mode 100644 index 65d1ce7..0000000 --- a/lib/intelligent_foods/authorization/blank.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - module Authorization - class Blank < Base - def header; end - end - end -end diff --git a/lib/intelligent_foods/errors.rb b/lib/intelligent_foods/errors.rb deleted file mode 100644 index 3e9f8ac..0000000 --- a/lib/intelligent_foods/errors.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class ResourceRetrievalError < ApiError; end - - class OrderNotCancelledError < ApiError; end - - class OrderNotCreatedError < ApiError; end - - class AuthenticationError < ApiError; end -end diff --git a/lib/intelligent_foods/resources/api_error.rb b/lib/intelligent_foods/resources/api_error.rb deleted file mode 100644 index a45f679..0000000 --- a/lib/intelligent_foods/resources/api_error.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class ApiError < StandardError - attr_reader :status, :title, :details - - def initialize(status:, title:, details: nil) - @status = status - @title = title - @details = details - super - end - - def message - if details.blank? - "#{status} - #{title}" - else - "#{status} #{title} - #{description}" - end - end - - def self.build(response) - data = response.data - data => { status:, title:, details: } - new(status: status, title: title, details: details) - end - - protected - - def description - details[:description] - end - end -end diff --git a/lib/intelligent_foods/resources/inventory_level.rb b/lib/intelligent_foods/resources/inventory_level.rb deleted file mode 100644 index f95b867..0000000 --- a/lib/intelligent_foods/resources/inventory_level.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class InventoryLevel < IntelligentFoods::Object - def object_name - "inventory-level" - end - - def resources_path - "#{api.base_url}/products/inventory-levels" - end - - def self.retrieve_all - resource = new - api_client = resource.client - response = api_client.get(path: resource.resources_path) - if response.success? - response.data[:products].map { |product| build(product) } - else - raise ResourceRetrievalError.build(response) - end - end - end -end diff --git a/lib/intelligent_foods/resources/menu.rb b/lib/intelligent_foods/resources/menu.rb deleted file mode 100644 index 8f7657f..0000000 --- a/lib/intelligent_foods/resources/menu.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class Menu < IntelligentFoods::Object - def initialize(args = {}) - super - end - - def object_name - "menu" - end - - def resources_path - "#{api.base_url}/menus" - end - - def self.build_from_response(data) - menu = build(data) - menu.items = MenuItem.build(data[:items]) - menu - end - end -end diff --git a/lib/intelligent_foods/resources/menu_item.rb b/lib/intelligent_foods/resources/menu_item.rb deleted file mode 100644 index 1a885fd..0000000 --- a/lib/intelligent_foods/resources/menu_item.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class MenuItem < IntelligentFoods::Object - def initialize(args = {}) - super - end - end -end diff --git a/lib/intelligent_foods/resources/object.rb b/lib/intelligent_foods/resources/object.rb deleted file mode 100644 index 653944d..0000000 --- a/lib/intelligent_foods/resources/object.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class Object < OpenStruct - extend IntelligentFoods::ApiOperations::Retrieve - - def self.build(data) - JSON.parse(data.to_json, object_class: self) - end - - def resources_path - "#{api.base_url}/#{object_name.downcase}" - end - - def resource_path - "#{api.base_url}/#{object_name.downcase}/#{id}" - end - - def client - api.client - end - - def api - @api ||= IntelligentFoods::V1.build - end - end -end diff --git a/lib/intelligent_foods/resources/order.rb b/lib/intelligent_foods/resources/order.rb deleted file mode 100644 index 25c837b..0000000 --- a/lib/intelligent_foods/resources/order.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class Order < IntelligentFoods::Object - ACCEPTED = "accepted" - CANCELLED = "cancelled" - ERROR = "error" - IN_PROCESS = "in_process" - INITIALIZED = "initialized" - PROCESSED = "processed" - - attr_reader :skip_temperature_check, :skip_address_check - - def initialize(skip_temperature_check: false, skip_address_check: false, **) - super - @skip_temperature_check = skip_temperature_check - @skip_address_check = skip_address_check - end - - def self.build_from_response(data) - order = build(data) - order[:items] = OrderItem.build(data[:items]) - order[:ship_to] = Recipient.build(data[:ship_to]) - order - end - - def object_name - "order" - end - - def create! - response = client.post(path: resources_path, body: request_body) - if response.success? - Order::build_from_response(response.data) - else - handle_order_not_created(response) - end - end - - def cancel! - response = client.delete(path: resource_path) - if response.success? - mark_as_cancelled - self - else - handle_order_not_cancelled(response) - end - end - - def request_body - @request_body ||= { - menu_id: menu.id, - reference_id: external_id.to_s, - ship_to: ship_to, - delivery_date: delivery_date, - items: items_json, - validation_options: validation_options, - callback_url: callback_url, - callback_headers: callback_headers, - } - end - - def cancelled? - status.downcase == CANCELLED - end - - def valid? - status.downcase != ERROR - end - - def accepted? - status.downcase == ACCEPTED - end - - protected - - def handle_order_not_created(response) - mark_as_invalid - raise OrderNotCreatedError.build(response) - end - - def handle_order_not_cancelled(response) - mark_as_invalid - raise OrderNotCancelledError.build(response) - end - - def mark_as_cancelled - self[:status] = CANCELLED - end - - def mark_as_invalid - self[:status] = ERROR - end - - def items_json - return if items.nil? - - items.map do |item| - OrderItemSerializer.new(item).to_json - end - end - - def ship_to - RecipientSerializer.new(recipient).to_json - end - - def validation_options - { - skip_temperature_check: skip_temperature_check, - skip_address_check: skip_address_check, - } - end - end -end diff --git a/lib/intelligent_foods/resources/order_item.rb b/lib/intelligent_foods/resources/order_item.rb deleted file mode 100644 index f75de9c..0000000 --- a/lib/intelligent_foods/resources/order_item.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class OrderItem < IntelligentFoods::Object - def initialize(args = {}) - super - end - end -end diff --git a/lib/intelligent_foods/resources/recipient.rb b/lib/intelligent_foods/resources/recipient.rb deleted file mode 100644 index e3d3a46..0000000 --- a/lib/intelligent_foods/resources/recipient.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class Recipient < IntelligentFoods::Object - def initialize(args = {}) - super - end - end -end - diff --git a/lib/intelligent_foods/rspec.rb b/lib/intelligent_foods/rspec.rb deleted file mode 100644 index b95b28e..0000000 --- a/lib/intelligent_foods/rspec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require "rspec/rails" -require "intelligent_foods/testing/fake" -require "intelligent_foods/testing/fake/callback" - -RSpec.configure do |config| - config.before(:each) do - stub_const("IntelligentFoods", IntelligentFoods::Testing::Fake) - end -end diff --git a/lib/intelligent_foods/serializers/order_item_serializer.rb b/lib/intelligent_foods/serializers/order_item_serializer.rb deleted file mode 100644 index f77dee8..0000000 --- a/lib/intelligent_foods/serializers/order_item_serializer.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class OrderItemSerializer < SimpleDelegator - def to_json - { - sku: sku, - protein_sku: protein_sku, - quantity: quantity, - } - end - end -end diff --git a/lib/intelligent_foods/serializers/recipient_serializer.rb b/lib/intelligent_foods/serializers/recipient_serializer.rb deleted file mode 100644 index baa5e15..0000000 --- a/lib/intelligent_foods/serializers/recipient_serializer.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - class RecipientSerializer < SimpleDelegator - def to_json - recipient_object = { - name: name, - street1: street1, - street2: street2, - city: city, - state: state, - zip: zip, - zip4: zip4, - email: email, - phone: phone, - delivery_instructions: delivery_instructions, - } - remove_blank_values(recipient_object) - end - - protected - - def remove_blank_values(obj) - obj.delete_if do |_k, v| - v.blank? - end - end - end -end diff --git a/lib/intelligent_foods/version.rb b/lib/intelligent_foods/version.rb deleted file mode 100644 index aa55aaa..0000000 --- a/lib/intelligent_foods/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module IntelligentFoods - VERSION = "0.0.0" -end diff --git a/sig/intelligent_foods.rbs b/sig/intelligent_foods.rbs index c7207bb..d33564e 100644 --- a/sig/intelligent_foods.rbs +++ b/sig/intelligent_foods.rbs @@ -1,3 +1,3 @@ -module IntelligentFoods +module Core VERSION: String end diff --git a/spec/intelligent_foods/api/v1/authenticator_spec.rb b/spec/core/api/v1/authenticator_spec.rb similarity index 69% rename from spec/intelligent_foods/api/v1/authenticator_spec.rb rename to spec/core/api/v1/authenticator_spec.rb index 9356d11..ace0f4d 100644 --- a/spec/intelligent_foods/api/v1/authenticator_spec.rb +++ b/spec/core/api/v1/authenticator_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -RSpec.describe IntelligentFoods::V1::Authenticator do +RSpec.describe Bls::Core::V1::Authenticator do describe "#save!" do it "returns a authorization scheme" do - auth = IntelligentFoods::V1::Authenticator. + auth = Bls::Core::V1::Authenticator. new(base_url: "http://test.com", client_id: "id123", client_secret: "secret12") stub_authentication @@ -15,7 +15,7 @@ context "when authentication fails" do it "raises a AuthenticationError" do - auth = IntelligentFoods::V1::Authenticator. + auth = Bls::Core::V1::Authenticator. new(base_url: "http://test.com", client_id: "id123", client_secret: "secret12") response_body = { error: "Authentication Failed" } @@ -24,32 +24,32 @@ expect { auth.save! - }.to raise_error(IntelligentFoods::AuthenticationError) + }.to raise_error(Bls::Core::AuthenticationError) end end end describe ".build" do it "sets the client_id" do - api = IntelligentFoods::V1.new(username: "id123") + api = Bls::Core::V1.new(username: "id123") - result = IntelligentFoods::V1::Authenticator.build(api) + result = Bls::Core::V1::Authenticator.build(api) expect(result.client_id).to eq("id123") end it "sets the client_secret" do - api = IntelligentFoods::V1.new(password: "secret123") + api = Bls::Core::V1.new(password: "secret123") - result = IntelligentFoods::V1::Authenticator.build(api) + result = Bls::Core::V1::Authenticator.build(api) expect(result.client_secret).to eq("secret123") end it "sets the base_url" do - api = IntelligentFoods::V1.new + api = Bls::Core::V1.new - result = IntelligentFoods::V1::Authenticator.build(api) + result = Bls::Core::V1::Authenticator.build(api) expect(result.base_url).to eq(api.base_url) end diff --git a/spec/intelligent_foods/api/v1_spec.rb b/spec/core/api/v1_spec.rb similarity index 68% rename from spec/intelligent_foods/api/v1_spec.rb rename to spec/core/api/v1_spec.rb index dbbc41a..6afb42a 100644 --- a/spec/intelligent_foods/api/v1_spec.rb +++ b/spec/core/api/v1_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -RSpec.describe IntelligentFoods::V1 do +RSpec.describe Bls::Core::V1 do describe "#base_url" do context "when the environment is production" do it "returns the correct url" do - api = IntelligentFoods::V1.new(environment: "production") + api = Bls::Core::V1.new(environment: "production") result = api.base_url @@ -14,7 +14,7 @@ context "when the environment is not production" do it "returns the correct url" do - api = IntelligentFoods::V1.new(environment: "staging") + api = Bls::Core::V1.new(environment: "staging") result = api.base_url @@ -25,7 +25,7 @@ describe "#client" do it "returns a api client" do - api = IntelligentFoods::V1.new + api = Bls::Core::V1.new build_stubbed_authenticator(api) result = api.client @@ -36,16 +36,16 @@ describe "#authenticate" do it "builds a authenticator" do - api = IntelligentFoods::V1.new + api = Bls::Core::V1.new build_stubbed_authenticator(api) api.authenticate! - expect(IntelligentFoods::V1::Authenticator).to have_received(:build).once + expect(Bls::Core::V1::Authenticator).to have_received(:build).once end it "authenticates" do - api = IntelligentFoods::V1.new + api = Bls::Core::V1.new authenticator = build_stubbed_authenticator(api) api.authenticate! @@ -55,7 +55,7 @@ context "when the authentication is present" do it "is authenticated" do - api = IntelligentFoods::V1.new + api = Bls::Core::V1.new build_stubbed_authenticator(api) api.authenticate! @@ -67,8 +67,8 @@ describe "reset authentication" do it "removes the present authentication" do - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") - api = IntelligentFoods::V1.new(authentication: auth) + auth = Bls::Core::Authorization::Bearer.new(token: "1234") + api = Bls::Core::V1.new(authentication: auth) result = api.reset_authentication @@ -76,8 +76,8 @@ end it "is not authenticated" do - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") - api = IntelligentFoods::V1.new(authentication: auth) + auth = Bls::Core::Authorization::Bearer.new(token: "1234") + api = Bls::Core::V1.new(authentication: auth) result = api.reset_authentication @@ -89,7 +89,7 @@ it "sets the environment" do config = build_config(environment: "preview") - result = IntelligentFoods::V1.build(config: config) + result = Bls::Core::V1.build(config: config) expect(result.environment).to eq("preview") end @@ -97,7 +97,7 @@ it "sets the username" do config = build_config(client_id: "secretUsername") - result = IntelligentFoods::V1.build(config: config) + result = Bls::Core::V1.build(config: config) expect(result.username).to eq("secretUsername") end @@ -105,7 +105,7 @@ it "sets the password" do config = build_config(client_secret: "secretPassword") - result = IntelligentFoods::V1.build(config: config) + result = Bls::Core::V1.build(config: config) expect(result.password).to eq("secretPassword") end @@ -118,10 +118,10 @@ def build_config(environment: "staging", client_id: "cid_123", end def build_stubbed_authenticator(api) - authenticator = IntelligentFoods::V1::Authenticator.build(api) - authentication = IntelligentFoods::Authorization::Bearer.new(token: "123") - client = IntelligentFoods::ApiClient.new(authentication: authentication) - allow(IntelligentFoods::V1::Authenticator).to receive(:build). + authenticator = Bls::Core::V1::Authenticator.build(api) + authentication = Bls::Core::Authorization::Bearer.new(token: "123") + client = Bls::Core::ApiClient.new(authentication: authentication) + allow(Bls::Core::V1::Authenticator).to receive(:build). and_return(authenticator) allow(authenticator).to receive(:save!).and_return(client) authenticator diff --git a/spec/intelligent_foods/api_client_spec.rb b/spec/core/api_client_spec.rb similarity index 76% rename from spec/intelligent_foods/api_client_spec.rb rename to spec/core/api_client_spec.rb index fdcf0ec..5a4c754 100644 --- a/spec/intelligent_foods/api_client_spec.rb +++ b/spec/core/api_client_spec.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true -RSpec.describe IntelligentFoods::ApiClient do +RSpec.describe Bls::Core::ApiClient do describe ".build" do it "sets the api" do - api = IntelligentFoods::V1.new + api = Bls::Core::V1.new - result = IntelligentFoods::ApiClient.build(api) + result = Bls::Core::ApiClient.build(api) expect(result.api).to eq(api) end it "sets the authentication" do - api = IntelligentFoods::V1.new - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") + api = Bls::Core::V1.new + auth = Bls::Core::Authorization::Bearer.new(token: "1234") allow(api).to receive(:authentication).and_return(auth) - result = IntelligentFoods::ApiClient.build(api) + result = Bls::Core::ApiClient.build(api) expect(result.authentication).to eq(auth) end @@ -25,11 +25,11 @@ it "sets the authorization bearer header" do stub_api_response request = build_stubbed_post - api = IntelligentFoods::V1.new - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") - client = IntelligentFoods::ApiClient.new(api: api, + api = Bls::Core::V1.new + auth = Bls::Core::Authorization::Bearer.new(token: "1234") + client = Bls::Core::ApiClient.new(api: api, authentication: auth) - allow(IntelligentFoods::Authorization::Bearer).to receive(:new). + allow(Bls::Core::Authorization::Bearer).to receive(:new). and_return(auth) header = "Bearer 1234" @@ -43,7 +43,7 @@ http_client = double allow(http_client).to receive(:request) stub_api_response http: http_client - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new client.execute_request(request: request, uri: request.uri) @@ -55,7 +55,7 @@ http_client = double response = OpenStruct.new(code: 200) stub_api_response response: response, http: http_client - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(JSON).to receive(:parse) client.execute_request(request: request, uri: request.uri) @@ -69,7 +69,7 @@ http_client = double response = OpenStruct.new(code: 204) stub_api_response response: response, http: http_client - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(JSON).to receive(:parse) client.execute_request(request: request, uri: request.uri) @@ -84,7 +84,7 @@ http_client = double response = OpenStruct.new(code: 301) stub_api_response response: response, http: http_client - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(JSON).to receive(:parse) client.execute_request(request: request, uri: request.uri) @@ -94,17 +94,17 @@ end context "the response code is 401" do - it "raises a IntelligentFoods::AuthenticationError" do + it "raises a Bls::Core::AuthenticationError" do request = build_stubbed_post http_client = double response = OpenStruct.new(code: 401) stub_api_response response: response, http: http_client - api = IntelligentFoods::V1.new - client = IntelligentFoods::ApiClient.new(api: api) + api = Bls::Core::V1.new + client = Bls::Core::ApiClient.new(api: api) expect { client.execute_request(request: request, uri: request.uri) - }.to raise_error(IntelligentFoods::AuthenticationError) + }.to raise_error(Bls::Core::AuthenticationError) end it "resets the apis authentication" do @@ -112,13 +112,13 @@ http_client = double response = OpenStruct.new(code: 401) stub_api_response response: response, http: http_client - api = IntelligentFoods::V1.new - client = IntelligentFoods::ApiClient.new(api: api) + api = Bls::Core::V1.new + client = Bls::Core::ApiClient.new(api: api) allow(api).to receive(:reset_authentication) begin client.execute_request(request: request, uri: request.uri) - rescue IntelligentFoods::AuthenticationError + rescue Bls::Core::AuthenticationError end expect(api).to have_received(:reset_authentication) @@ -130,7 +130,7 @@ it "performs a post request" do stub_api_response body = { test: "yes" } - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(Net::HTTP::Post).to receive(:new).and_call_original client.post(path: "http://test.com/orders", body: body) @@ -141,7 +141,7 @@ it "assigns a body to the request" do stub_api_response body = { test: "yes" } - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new path = "http://test.com/orders" request = Net::HTTP::Post.new(URI(path)) allow(Net::HTTP::Post).to receive(:new).and_return(request) @@ -154,7 +154,7 @@ it "executes the request" do stub_api_response body = { test: "yes" } - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(client).to receive(:execute_request).and_call_original client.post(path: "http://test.com/orders", body: body) @@ -166,8 +166,8 @@ it "executes the request with authorization" do stub_api_response body = { test: "yes" } - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") - client = IntelligentFoods::ApiClient.new(authentication: auth) + auth = Bls::Core::Authorization::Bearer.new(token: "1234") + client = Bls::Core::ApiClient.new(authentication: auth) request = Net::HTTP::Post.new(URI("http://test.com/orders")) allow(Net::HTTP::Post).to receive(:new).and_return(request) @@ -181,7 +181,7 @@ describe "#delete" do it "performs a delete request" do stub_api_response - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(Net::HTTP::Delete).to receive(:new).and_call_original client.delete(path: "http://test.com/orders/1") @@ -192,8 +192,8 @@ context "when authorization is provided" do it "executes the request with authorization" do stub_api_response - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") - client = IntelligentFoods::ApiClient.new(authentication: auth) + auth = Bls::Core::Authorization::Bearer.new(token: "1234") + client = Bls::Core::ApiClient.new(authentication: auth) request = Net::HTTP::Delete.new(URI("http://test.com/orders/1")) allow(Net::HTTP::Delete).to receive(:new).and_return(request) @@ -207,7 +207,7 @@ describe "#get" do it "performs a get request" do stub_api_response - client = IntelligentFoods::ApiClient.new + client = Bls::Core::ApiClient.new allow(Net::HTTP::Get).to receive(:new).and_call_original client.get(path: "http://test.com/orders/1") @@ -218,8 +218,8 @@ context "when authorization is provided" do it "executes the request with authorization" do stub_api_response - auth = IntelligentFoods::Authorization::Bearer.new(token: "1234") - client = IntelligentFoods::ApiClient.new(authentication: auth) + auth = Bls::Core::Authorization::Bearer.new(token: "1234") + client = Bls::Core::ApiClient.new(authentication: auth) request = Net::HTTP::Get.new(URI("http://test.com/orders/1")) allow(Net::HTTP::Get).to receive(:new).and_return(request) diff --git a/spec/intelligent_foods/resources/inventory_level_spec.rb b/spec/core/resources/inventory_level_spec.rb similarity index 77% rename from spec/intelligent_foods/resources/inventory_level_spec.rb rename to spec/core/resources/inventory_level_spec.rb index 4be4a93..9bd56e5 100644 --- a/spec/intelligent_foods/resources/inventory_level_spec.rb +++ b/spec/core/resources/inventory_level_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe IntelligentFoods::InventoryLevel do +RSpec.describe Bls::Core::InventoryLevel do describe ".retrieve_all" do it "returns the product inventory levels" do body = build_inventory_levels_response @@ -9,7 +9,7 @@ stub_api_response response: response product_id = "MP0527" - result = IntelligentFoods::InventoryLevel.retrieve_all + result = Bls::Core::InventoryLevel.retrieve_all expect(result.map(&:product_code)).to match_array(product_id) end diff --git a/spec/intelligent_foods/resources/menu_spec.rb b/spec/core/resources/menu_spec.rb similarity index 77% rename from spec/intelligent_foods/resources/menu_spec.rb rename to spec/core/resources/menu_spec.rb index ca8a82a..54b4196 100644 --- a/spec/intelligent_foods/resources/menu_spec.rb +++ b/spec/core/resources/menu_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe IntelligentFoods::Menu do +RSpec.describe Bls::Core::Menu do describe ".retrieve_all" do it "returns the menus" do menu_ids = ["2023-03-12"] @@ -8,7 +8,7 @@ stub_api_v1_authentication stub_api_response response: response - result = IntelligentFoods::Menu.retrieve_all + result = Bls::Core::Menu.retrieve_all expect(result.map(&:id)).to eq(menu_ids) end @@ -22,7 +22,7 @@ stub_api_v1_authentication stub_api_response response: response - result = IntelligentFoods::Menu.retrieve(menu_id) + result = Bls::Core::Menu.retrieve(menu_id) expect(result.id).to eq(menu_id) end @@ -35,7 +35,7 @@ response = build_response(body: body) stub_api_v1_authentication stub_api_response response: response - menu = IntelligentFoods::Menu.retrieve(menu_id) + menu = Bls::Core::Menu.retrieve(menu_id) result = menu.items.size @@ -43,15 +43,15 @@ end context "the id does not match a menu" do - it "raises a IntelligentFoods::ResourceRetrievalError error" do + it "raises a Bls::Core::ResourceRetrievalError error" do menu_id = "2023-01-01" response = error_response(message: "Menu not found", http_status_code: 400) stub_api_response response: response expect { - IntelligentFoods::Menu.retrieve(menu_id) - }.to raise_error(IntelligentFoods::ResourceRetrievalError) + Bls::Core::Menu.retrieve(menu_id) + }.to raise_error(Bls::Core::ResourceRetrievalError) end end end diff --git a/spec/intelligent_foods/resources/order_spec.rb b/spec/core/resources/order_spec.rb similarity index 83% rename from spec/intelligent_foods/resources/order_spec.rb rename to spec/core/resources/order_spec.rb index d91ae51..2487132 100644 --- a/spec/intelligent_foods/resources/order_spec.rb +++ b/spec/core/resources/order_spec.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -RSpec.describe IntelligentFoods::Order do +RSpec.describe Bls::Core::Order do describe "#create!" do it "creates the order" do recipient = build(:recipient) menu = build(:menu, id: "2023-01-01") order_item = build(:order_item) - order = IntelligentFoods::Order.new(menu: menu, + order = Bls::Core::Order.new(menu: menu, recipient: recipient, delivery_date: "2023-01-07", items: [order_item], @@ -36,9 +36,9 @@ it "assigns the recipient in the request body" do recipient = build(:recipient) - ship_to = IntelligentFoods::RecipientSerializer.new(recipient).to_json + ship_to = Bls::Core::RecipientSerializer.new(recipient).to_json menu = build(:menu, id: "2023-01-01") - order = IntelligentFoods::Order.new(recipient: recipient, menu: menu) + order = Bls::Core::Order.new(recipient: recipient, menu: menu) body = build_order_response response = build_response(body: body) stub_api_response response: response @@ -52,7 +52,7 @@ it "excludes it from the request body" do recipient = build(:recipient, street2: "") menu = build(:menu, id: "2023-01-01") - order = IntelligentFoods::Order.new(recipient: recipient, menu: menu) + order = Bls::Core::Order.new(recipient: recipient, menu: menu) body = build_order_response response = build_response(body: body) stub_api_response response: response @@ -67,10 +67,10 @@ recipient = build(:recipient) menu = build(:menu, id: "2023-01-01") order_item = build(:order_item, quantity: 2) - order_item_serialized = IntelligentFoods::OrderItemSerializer. + order_item_serialized = Bls::Core::OrderItemSerializer. new(order_item). to_json - order = IntelligentFoods::Order.new(recipient: recipient, menu: menu, + order = Bls::Core::Order.new(recipient: recipient, menu: menu, items: [order_item]) body = build_order_response response = build_response(body: body) @@ -86,7 +86,7 @@ recipient = build(:recipient) menu = build(:menu, id: "2023-01-01") order_item = build(:order_item, quantity: 2) - order = IntelligentFoods::Order.new(menu: menu, + order = Bls::Core::Order.new(menu: menu, recipient: recipient, items: [order_item], external_id: 1234) @@ -103,27 +103,27 @@ it "raises a OrderNotCreatedError" do recipient = build(:recipient) menu = build(:menu, id: "2023-01-01") - order = IntelligentFoods::Order.new(recipient: recipient, menu: menu) + order = Bls::Core::Order.new(recipient: recipient, menu: menu) response = error_response(message: "Order Not Created", http_status_code: 400) stub_api_response response: response expect { order.create! - }.to raise_error(IntelligentFoods::OrderNotCreatedError) + }.to raise_error(Bls::Core::OrderNotCreatedError) end it "marks the order as invalid" do recipient = build(:recipient) menu = build(:menu, id: "2023-01-01") - order = IntelligentFoods::Order.new(recipient: recipient, menu: menu) + order = Bls::Core::Order.new(recipient: recipient, menu: menu) response = error_response(message: "Order Not Created", http_status_code: 400) stub_api_response response: response begin order.create! - rescue IntelligentFoods::OrderNotCreatedError + rescue Bls::Core::OrderNotCreatedError # noop end @@ -136,7 +136,7 @@ recipient = build(:recipient) menu = build(:menu, id: "2023-01-01") order_item = build(:order_item, quantity: 2) - order = IntelligentFoods::Order.new(recipient: recipient, menu: menu, + order = Bls::Core::Order.new(recipient: recipient, menu: menu, items: [order_item], skip_temperature_check: true) body = build_order_response @@ -155,7 +155,7 @@ describe "#cancel!" do it "cancels the order" do - order = IntelligentFoods::Order.new(id: 1) + order = Bls::Core::Order.new(id: 1) response = build_response(body: nil, http_status_code: 204) stub_api_response response: response @@ -166,25 +166,25 @@ context "the response code is not 204" do it "raises a OrderNotCancelledError" do - order = IntelligentFoods::Order.new(id: 1) + order = Bls::Core::Order.new(id: 1) response = error_response(message: "Order Not Found", http_status_code: 400) stub_api_response response: response expect { order.cancel! - }.to raise_error(IntelligentFoods::OrderNotCancelledError) + }.to raise_error(Bls::Core::OrderNotCancelledError) end it "marks the order as not valid" do - order = IntelligentFoods::Order.new(id: 1) + order = Bls::Core::Order.new(id: 1) response = error_response(message: "Order Not Found", http_status_code: 400) stub_api_response response: response begin order.cancel! - rescue IntelligentFoods::OrderNotCancelledError + rescue Bls::Core::OrderNotCancelledError # noop end diff --git a/spec/core_spec.rb b/spec/core_spec.rb new file mode 100644 index 0000000..ee0fdf8 --- /dev/null +++ b/spec/core_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.describe Bls::Core do + it "has a version number" do + expect(Bls::Core::VERSION).not_to be_nil + end + + describe "#configure" do + it "sets the client ID" do + Bls::Core.configure do |config| + config.client_id = "abc" + end + + expect(Bls::Core.client_id).to eq("abc") + end + + it "sets the client secret" do + Bls::Core.configure do |config| + config.client_secret = "abc" + end + + expect(Bls::Core.client_secret).to eq("abc") + end + + it "sets the environment" do + Bls::Core.configure do |config| + config.environment = "preview" + end + + expect(Bls::Core.environment).to eq("preview") + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 12a5910..57bc36d 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :order, class: "IntelligentFoods::Order" do + factory :order, class: "Bls::Core::Order" do menu recipient delivery_date { Date.today.to_s } @@ -14,7 +14,7 @@ end end - factory :recipient, class: "IntelligentFoods::Recipient" do + factory :recipient, class: "Bls::Core::Recipient" do name { "First Name" } street1 { "123 Main Street" } street2 { "Apt 2B" } @@ -27,14 +27,14 @@ delivery_instructions { "Door code 1234" } end - factory :menu, class: "IntelligentFoods::Menu" do + factory :menu, class: "Bls::Core::Menu" do id { Date.today.to_s } deadline { Time.now } shipping_fee { 9.99 } items { create_list :menu_item, 3 } end - factory :menu_item, class: "IntelligentFoods::MenuItem" do + factory :menu_item, class: "Bls::Core::MenuItem" do sequence :id do |n| "d89397e1bad49c3b855df4406e5bf0#{n}" end @@ -47,7 +47,7 @@ end end - factory :order_item, class: "IntelligentFoods::OrderItem" do + factory :order_item, class: "Bls::Core::OrderItem" do sequence :sku do |n| "IF233#{n}" end diff --git a/spec/intelligent_foods_spec.rb b/spec/intelligent_foods_spec.rb deleted file mode 100644 index eb5f719..0000000 --- a/spec/intelligent_foods_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe IntelligentFoods do - it "has a version number" do - expect(IntelligentFoods::VERSION).not_to be_nil - end - - describe "#configure" do - it "sets the client ID" do - IntelligentFoods.configure do |config| - config.client_id = "abc" - end - - expect(IntelligentFoods.client_id).to eq("abc") - end - - it "sets the client secret" do - IntelligentFoods.configure do |config| - config.client_secret = "abc" - end - - expect(IntelligentFoods.client_secret).to eq("abc") - end - - it "sets the environment" do - IntelligentFoods.configure do |config| - config.environment = "preview" - end - - expect(IntelligentFoods.environment).to eq("preview") - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 31ff96c..49d7dc2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,7 +3,7 @@ require "factory_bot" require "json-schema" require "pry" -require "intelligent_foods" +require "core" require "webmock" Dir["./spec/support/**/*.rb"].sort.each { |f| require f } diff --git a/spec/support/helpers/api_helper.rb b/spec/support/helpers/api_helper.rb index 67422ac..e64195b 100644 --- a/spec/support/helpers/api_helper.rb +++ b/spec/support/helpers/api_helper.rb @@ -23,8 +23,8 @@ def stub_authentication(access_token: "indifferenttoken") end def stub_api_v1_authentication(authenticated: true) - api = IntelligentFoods::V1.build - allow(IntelligentFoods::V1).to receive(:new).and_return(api) + api = Bls::Core::V1.build + allow(Bls::Core::V1).to receive(:new).and_return(api) allow(api).to receive(:authenticated?).and_return(authenticated) end