Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion lib/intelligent_foods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,29 @@

require "intelligent_foods/api_client"
require "intelligent_foods/api/v1"
require "intelligent_foods/api/v2"
require "intelligent_foods/api/v1/authenticator"
require "intelligent_foods/api/v2/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/core_object"
require "intelligent_foods/resources/allergen"
require "intelligent_foods/resources/dietary_tag"
require "intelligent_foods/resources/inventory_level"
require "intelligent_foods/resources/ingredient"
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/nutrition_fact"
require "intelligent_foods/resources/product"
require "intelligent_foods/resources/recipient"
require "intelligent_foods/serializers/recipient_serializer"
require "intelligent_foods/version"
Expand All @@ -31,7 +39,7 @@ module IntelligentFoods
class Error < StandardError; end

class << self
attr_accessor :client_id, :client_secret, :environment
attr_accessor :client_id, :client_secret, :environment, :username, :password

def configure
yield self
Expand Down
68 changes: 68 additions & 0 deletions lib/intelligent_foods/api/v2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# frozen_string_literal: true

module IntelligentFoods
class V2
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://core.intelligentfoods.#{tld}"
end

def authenticate!
return if authenticated?

authenticator = V2::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.username,
password: config.password)
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
44 changes: 44 additions & 0 deletions lib/intelligent_foods/api/v2/authenticator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module IntelligentFoods
class V2::Authenticator
attr_accessor :base_url, :authentication

def initialize(base_url:, authentication:)
@base_url = base_url
@authentication = authentication
end

def save!
path = "#{base_url}/auth/info"
client = IntelligentFoods::ApiClient.new(authentication: authentication)
response = client.get(path: path)
handle_authentication_response!(response: response)
end

def self.build(api)
authentication = Authorization::Basic.
factory(client_id: api.username,
client_secret: api.password)
new(base_url: api.base_url, authentication: authentication)
end

protected

def handle_authentication_response!(response:)
if response.success?
authentication
else
handle_errors!
end
end

def response_has_errors?(response)
response.has_key?(:error)
end

def handle_errors!
raise AuthenticationError.new(status: 401, title: "Authentication Failed")
end
end
end
5 changes: 5 additions & 0 deletions lib/intelligent_foods/resources/allergen.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module IntelligentFoods
class Allergen < IntelligentFoods::CoreObject; end
end
4 changes: 3 additions & 1 deletion lib/intelligent_foods/resources/api_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ def message

def self.build(response)
data = response.data
data => { status:, title:, details: }
status = data.fetch(:status, data[:type])
title = data.fetch(:title, data[:message])
details = data[:details]
new(status: status, title: title, details: details)
end

Expand Down
17 changes: 17 additions & 0 deletions lib/intelligent_foods/resources/core_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module IntelligentFoods
class CoreObject < IntelligentFoods::Object
def resources_path
"#{api.base_url}/api/#{object_name.downcase}"
end

def resource_path
"#{api.base_url}/api/#{object_name.downcase}/#{id}"
end

def api
@api ||= IntelligentFoods::V2.build
end
end
end
5 changes: 5 additions & 0 deletions lib/intelligent_foods/resources/dietary_tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module IntelligentFoods
class DietaryTag < IntelligentFoods::CoreObject; end
end
5 changes: 5 additions & 0 deletions lib/intelligent_foods/resources/ingredient.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module IntelligentFoods
class Ingredient < IntelligentFoods::CoreObject; end
end
5 changes: 5 additions & 0 deletions lib/intelligent_foods/resources/nutrition_fact.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module IntelligentFoods
class NutritionFact < IntelligentFoods::CoreObject; end
end
4 changes: 4 additions & 0 deletions lib/intelligent_foods/resources/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def self.build(data)
JSON.parse(data.to_json, object_class: self)
end

def self.build_from_array(array)
array.map { |item| build(item) }
end

def resources_path
"#{api.base_url}/#{object_name.downcase}"
end
Expand Down
27 changes: 27 additions & 0 deletions lib/intelligent_foods/resources/product.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module IntelligentFoods
class Product < IntelligentFoods::CoreObject
def initialize(args = {})
super
end

def object_name
"products"
end

def self.build_from_response(data)
product = build(data)
product.ingredients = Ingredient.build_from_array(data[:ingredients])
product.nutrition_facts = NutritionFact.
build_from_array(data[:nutrition_facts])
product.nutrition_facts_cooked = NutritionFact.
build_from_array(
data.fetch(:nutrition_facts_cooked, [])
)
product.allergens = Allergen.build_from_array(data[:allergens])
product.dietary_tags = DietaryTag.build_from_array(data[:dietary_tags])
product
end
end
end
15 changes: 15 additions & 0 deletions lib/intelligent_foods/testing/fake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ module Testing
class Fake
def self.configure(*); end

class Allergen
def self.new(code: "MILK", source: "ANCHOVY")
OpenStruct.new(code: code, source: source)
end
end

class DistributionCenterInventory
def self.create(name: "WEST_COAST", quantity: 100)
OpenStruct.new(distribution_center: name, quantity: quantity)
Expand Down Expand Up @@ -89,6 +95,15 @@ def self.new(sku:, quantity:, protein_sku:)
end
end

class Product
def self.new(code:, name:, status: "DRAFT", nutrition_facts: [])
OpenStruct.new(code: code, name: name, status: status,
nutrition_facts: nutrition_facts,
allergens: [Fake::Allergen.new],
dietary_tags: ["NUTS"])
end
end

def self.client
Fake::Client.new
end
Expand Down
58 changes: 58 additions & 0 deletions spec/intelligent_foods/api/v2/authenticator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

RSpec.describe IntelligentFoods::V2::Authenticator do
describe "#save!" do
it "returns a authorization scheme" do
basic = IntelligentFoods::Authorization::Basic.new(token: "test")
auth = IntelligentFoods::V2::Authenticator.
new(base_url: "http://test.com", authentication: basic)
stub_authentication

result = auth.save!

expect(result).to respond_to(:header)
end

context "when authentication fails" do
it "raises a AuthenticationError" do
basic = IntelligentFoods::Authorization::Basic.new(token: "test")
auth = IntelligentFoods::V2::Authenticator.
new(base_url: "http://test.com", authentication: basic)
response_body = { error: "Authentication Failed" }
response = error_response(body: response_body)
stub_api_response response: response

expect {
auth.save!
}.to raise_error(IntelligentFoods::AuthenticationError)
end
end
end

describe ".build" do
it "sets the base_url" do
api = IntelligentFoods::V2.new

result = IntelligentFoods::V2::Authenticator.build(api)

expect(result.base_url).to eq(api.base_url)
end

it "sets the authentication" do
api = IntelligentFoods::V2.new
basic = IntelligentFoods::Authorization::Basic.new(token: "test")
allow(IntelligentFoods::Authorization::Basic).to receive(:factory).
and_return(basic)

result = IntelligentFoods::V2::Authenticator.build(api)

expect(result.authentication).to eq(basic)
end
end

def build_config(environment: "staging", client_id: "cid_123",
client_secret: "cs_123")
OpenStruct.new(environment: environment, client_id: client_id,
client_secret: client_secret)
end
end
Loading