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
4 changes: 3 additions & 1 deletion lib/intelligent_foods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

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"
Expand All @@ -31,7 +33,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!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why save? i don't think anything is actually being committed anywhere is it? Maybe authenticate! is clearer

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class has to have the same public interface as the authenticator for v1 which also uses #save! (link). This decision was made initially to make the public api of the classes align with active record. Renaming would impact existing v1 classes, which if necessary should probably be done separately if we're now reconsidering naming.

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
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
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
129 changes: 129 additions & 0 deletions spec/intelligent_foods/api/v2_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# frozen_string_literal: true

RSpec.describe IntelligentFoods::V2 do
describe "#base_url" do
context "when the environment is production" do
it "returns the correct url" do
api = IntelligentFoods::V2.new(environment: "production")

result = api.base_url

expect(result).to eq("https://core.intelligentfoods.com")
end
end

context "when the environment is not production" do
it "returns the correct url" do
api = IntelligentFoods::V2.new(environment: "staging")

result = api.base_url

expect(result).to eq("https://core.intelligentfoods.dev")
end
end
end

describe "#client" do
it "returns a api client" do
api = IntelligentFoods::V2.new
build_stubbed_authenticator(api)

result = api.client

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

describe "#authenticate" do
it "builds a authenticator" do
api = IntelligentFoods::V2.new
build_stubbed_authenticator(api)

api.authenticate!

expect(IntelligentFoods::V2::Authenticator).to have_received(:build).once
end

it "authenticates" do
api = IntelligentFoods::V2.new
authenticator = build_stubbed_authenticator(api)

api.authenticate!

expect(authenticator).to have_received(:save!).once
end

context "when the authentication is present" do
it "is authenticated" do
api = IntelligentFoods::V2.new
build_stubbed_authenticator(api)

api.authenticate!

expect(api).to be_authenticated
end
end
end

describe "reset authentication" do
it "removes the present authentication" do
auth = IntelligentFoods::Authorization::Basic.new(token: "1234")
api = IntelligentFoods::V2.new(authentication: auth)

result = api.reset_authentication

expect(result.authentication).to be_blank
end

it "is not authenticated" do
auth = IntelligentFoods::Authorization::Basic.new(token: "1234")
api = IntelligentFoods::V2.new(authentication: auth)

result = api.reset_authentication

expect(result).not_to be_authenticated
end
end

describe ".build" do
it "sets the environment" do
config = build_config(environment: "preview")

result = IntelligentFoods::V2.build(config: config)

expect(result.environment).to eq("preview")
end

it "sets the username" do
config = build_config(username: "secretUsername")

result = IntelligentFoods::V2.build(config: config)

expect(result.username).to eq("secretUsername")
end

it "sets the password" do
config = build_config(password: "secretPassword")

result = IntelligentFoods::V2.build(config: config)

expect(result.password).to eq("secretPassword")
end
end

def build_config(environment: "staging", username: "string",
password: "string")
OpenStruct.new(environment: environment, username: username,
password: password)
end

def build_stubbed_authenticator(api)
authenticator = IntelligentFoods::V2::Authenticator.build(api)
authentication = authenticator.authentication
client = IntelligentFoods::ApiClient.new(authentication: authentication)
allow(IntelligentFoods::V2::Authenticator).to receive(:build).
and_return(authenticator)
allow(authenticator).to receive(:save!).and_return(client)
authenticator
end
end
6 changes: 6 additions & 0 deletions spec/support/helpers/api_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def stub_api_v1_authentication(authenticated: true)
allow(api).to receive(:authenticated?).and_return(authenticated)
end

def stub_api_v2_authentication(authenticated: true)
api = IntelligentFoods::V2.build
allow(IntelligentFoods::V2).to receive(:new).and_return(api)
allow(api).to receive(:authenticated?).and_return(authenticated)
end

def stub_api_response(response: OpenStruct.new(body: "{}", code: 200),
http: double)
allow(Net::HTTP).to receive(:start).and_yield(http)
Expand Down