Skip to content
Merged
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
2 changes: 2 additions & 0 deletions app/blueprints/agreements/iaa_blueprint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ class IaaBlueprint < Blueprinter::Base
field :order_end_date, datetime_format: '%Y-%m-%d'
field :order_estimated_amount
field :order_status
field :ial2_users
field :authentications
end
end
9 changes: 9 additions & 0 deletions app/models/agreements/iaa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ class Iaa
include ActiveModel::Model

attr_accessor :gtc, :order
attr_writer :ial2_users, :authentications

delegate :gtc_number, to: :gtc
delegate :order_number, to: :order
delegate :mod_number, :start_date, :end_date, :estimated_amount, to: :gtc, prefix: true
delegate :mod_number, :start_date, :end_date, :estimated_amount, to: :order, prefix: true

def ial2_users
@ial2_users || 0
end

def authentications
@authentications || {}
end

def iaa_number
"#{gtc.gtc_number}-#{'%04d' % order.order_number}-#{'%04d' % order.mod_number}"
end
Expand Down
15 changes: 15 additions & 0 deletions app/models/agreements/iaa_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,19 @@ class Agreements::IaaOrder < ApplicationRecord
def partner_status
iaa_status.partner_name
end

def in_pop?(date)
raise ArgumentError unless date.respond_to?(:strftime)
return false if pop_range.blank?

pop_range.include?(date.to_date)
end

private

def pop_range
return unless start_date.present? && end_date.present?

start_date..end_date
end
end
6 changes: 5 additions & 1 deletion app/services/agreements/db/iaas_by_agency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ module Db
class IaasByAgency
def self.call
IaaGtc.
includes(:iaa_status, partner_account: :agency, iaa_orders: :iaa_status).
includes(
:iaa_status,
partner_account: :agency,
iaa_orders: %i[iaa_status integrations],
).
group_by { |gtc| gtc.partner_account.agency.abbreviation }.
transform_values do |gtcs|
gtcs.map do |gtc|
Expand Down
15 changes: 15 additions & 0 deletions app/services/agreements/db/sp_return_log_scan.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Agreements
module Db
class SpReturnLogScan
def self.call
SpReturnLog.
select(:id, :issuer, :ial, :user_id, :returned_at).
find_in_batches(batch_size: 10_000) do |batch|
batch.each do |return_log|
yield return_log
end
end
end
end
end
end
30 changes: 30 additions & 0 deletions app/services/agreements/iaa_usage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Agreements
class IaaUsage
attr_reader :authentications, :ial2_users

def initialize(order:)
@order = order
@issuers = order.integrations.pluck(:issuer)
@authentications = Hash.new(0)
@ial2_users = Set.new
end

# return self so that it can be used in #tranform_values!
def count(return_log)
issuer = return_log.issuer
return self unless issuers.include?(issuer) && order.in_pop?(return_log.returned_at)

@authentications[issuer] += 1

return self unless return_log.ial == 2

@ial2_users.add(return_log.user_id)

self
end

private

attr_reader :order, :issuers
end
end
22 changes: 19 additions & 3 deletions app/services/agreements/reports/partner_api_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,38 @@ def run

collect_account_data
collect_iaa_data
collect_usage_data
upload_json_files
true
end

private

attr_reader :accounts_by_agency, :agencies, :iaas_by_agency
attr_reader :accounts_by_agency, :agencies, :iaas_by_agency, :usage_summary

def collect_account_data
@accounts_by_agency = Agreements::Db::AccountsByAgency.call
@accounts_by_agency = Db::AccountsByAgency.call
@agencies = accounts_by_agency.keys
@accounts_by_agency = accounts_by_agency.transform_keys(&:abbreviation)
end

def collect_iaa_data
@iaas_by_agency = Agreements::Db::IaasByAgency.call
@iaas_by_agency = Db::IaasByAgency.call
end

def collect_usage_data
all_iaas = iaas_by_agency.values.flatten
@usage_summary = UsageSummarizer.call(iaas: all_iaas)

@iaas_by_agency.transform_values! do |iaas|
iaas.each do |iaa|
usage = usage_summary[:iaas][iaa.iaa_number]
next if usage.blank?

iaa.ial2_users = usage.ial2_users.size
iaa.authentications = usage.authentications
end
end
end

def upload_json_files
Expand Down
40 changes: 40 additions & 0 deletions app/services/agreements/usage_summarizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module Agreements
class UsageSummarizer
def self.call(**args)
new(**args).call
end

def initialize(iaas:)
@iaas = iaas
@iaas_by_issuer = map_iaas_to_issuers
@usage = {
iaas: empty_iaa_usage_hash,
}
end

def call
Db::SpReturnLogScan.call do |return_log|
@usage[:iaas].transform_values! { |iaa_usage| iaa_usage.count(return_log) }
end

usage
end

private

attr_reader :iaas, :iaas_by_issuer, :usage

def map_iaas_to_issuers
iaas.each_with_object(Hash.new([])) do |iaa, hash|
issuers = iaa.order.integrations.map(&:issuer)
issuers.each { |issuer| hash[issuer] << iaa }
end
end

def empty_iaa_usage_hash
iaas.each_with_object({}) do |iaa, hash|
hash[iaa.iaa_number] = IaaUsage.new(order: iaa.order)
end
end
end
end
10 changes: 10 additions & 0 deletions spec/blueprints/agreements/iaa_blueprint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
Agreements::Iaa.new(
gtc: gtc,
order: order,
ial2_users: 10,
authentications: {
'issuer1' => 100,
'issuer2' => 1_000,
},
)
end
let(:expected) do
Expand All @@ -51,6 +56,11 @@
order_end_date: '2021-12-31',
order_estimated_amount: '20000.53',
order_status: 'active',
ial2_users: 10,
authentications: {
'issuer1' => 100,
'issuer2' => 1_000,
},
},
],
}.to_json
Expand Down
28 changes: 28 additions & 0 deletions spec/models/agreements/iaa_order_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,32 @@
expect(order.partner_status).to eq('foo')
end
end

describe '#in_pop?' do
let(:order) do
build(
:iaa_order,
start_date: Time.zone.today,
end_date: Time.zone.today + 1.week,
)
end

it 'raises an argument error if a non-date/datetime is passed in' do
expect{ order.in_pop?('foo') }.to raise_error(ArgumentError)
end
it 'returns false if the start_date is nil' do
order.start_date = nil
expect(order.in_pop?(Time.zone.today + 1.day)).to be false
end
it 'returns false if the end_date is nil' do
order.end_date = nil
expect(order.in_pop?(Time.zone.today + 1.day)).to be false
end
it 'returns false if the date is outside the POP' do
expect(order.in_pop?(Time.zone.today - 1.day)).to be false
end
it 'returns true if the date is within the POP' do
expect(order.in_pop?(Time.zone.today + 1.day)).to be true
end
end
end
22 changes: 22 additions & 0 deletions spec/models/agreements/iaa_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,28 @@
it { is_expected.to delegate_method(:end_date).to(:order).with_prefix }
it { is_expected.to delegate_method(:estimated_amount).to(:order).with_prefix }

describe 'attributes' do
describe 'ial2_users' do
it 'defaults to zero' do
expect(iaa.ial2_users).to eq(0)
end
it 'can be set to a custom value' do
iaa.ial2_users = 10
expect(iaa.ial2_users).to eq(10)
end
end

describe 'authentications' do
it 'defaults to an empty hash' do
expect(iaa.authentications).to eq({})
end
it 'can be set to a custom value' do
iaa.authentications = { 'issuer1' => 10 }
expect(iaa.authentications).to eq({ 'issuer1' => 10 })
end
end
end

describe '#iaa_number' do
it 'returns the formatted IAA number' do
expect(iaa.iaa_number).to eq('LGABC210001-0001-0002')
Expand Down
9 changes: 9 additions & 0 deletions spec/models/sp_return_log_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'rails_helper'

RSpec.describe SpReturnLog, type: :model do
describe 'associations' do
subject { described_class.new }

it { is_expected.to belong_to(:user) }
end
end
25 changes: 25 additions & 0 deletions spec/services/agreements/db/sp_return_log_scan_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'rails_helper'

RSpec.describe Agreements::Db::SpReturnLogScan do
describe '.call' do
let(:issuers) { %w[issuer1 issuer2 issuer3] }

before do
issuers.each do |issuer|
create(
:sp_return_log,
issuer: issuer,
requested_at: Time.zone.now,
ial: 1,
)
end
end

it 'scans through the sp_return_logs table and yields the block for each record' do
output = []
described_class.call { |record| output << record.issuer }

expect(output).to match_array(issuers)
end
end
end
84 changes: 84 additions & 0 deletions spec/services/agreements/iaa_usage_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require 'rails_helper'

describe Agreements::IaaUsage do
let(:partner_account) { create(:partner_account) }
let(:integration) { create(:integration, partner_account: partner_account) }
let(:order) do
create(
:iaa_order,
iaa_gtc: create(:iaa_gtc, partner_account: partner_account),
start_date: Time.zone.today,
end_date: Time.zone.today + 7.days,
)
end
let(:usage_obj) { described_class.new(order: order) }
let(:user) { create(:user) }
let(:other_sp) { create(:service_provider) }

before { order.integrations << integration }

describe 'defaults' do
it 'returns an empty hash defaulting to zero for authentications' do
expect(usage_obj.authentications).to eq({})
expect(usage_obj.authentications['foo']).to eq(0)
end

it 'returns an empty set for ial2_users' do
expect(usage_obj.ial2_users).to eq(Set.new)
end
end

describe '#count' do
it 'correctly counts IAL1 authentications within the PoP' do
log =
create_return_log(sp: integration, user: user, ial: 1, returned: Time.zone.today + 1.day)
usage_obj.count(log)

expect(usage_obj.authentications).to eq({ integration.issuer => 1 })
expect(usage_obj.ial2_users).to eq(Set.new)
end

it 'correctly counts IAL2 authentications within the PoP' do
log =
create_return_log(sp: integration, user: user, ial: 2, returned: Time.zone.today + 1.day)
usage_obj.count(log)

expect(usage_obj.authentications).to eq({ integration.issuer => 1 })
expect(usage_obj.ial2_users).to eq(Set.new([user.id]))
end

it 'correctly skips return logs outside the PoP' do
log =
create_return_log(sp: integration, user: user, ial: 2, returned: Time.zone.today - 1.day)

expect { usage_obj.count(log) }.not_to change { usage_obj.authentications }
expect { usage_obj.count(log) }.not_to change { usage_obj.ial2_users }
end

it 'correctly skipes return logs for other SPs' do
log =
create_return_log(sp: other_sp, user: user, ial: 2, returned: Time.zone.today + 1.day)

expect { usage_obj.count(log) }.not_to change { usage_obj.authentications }
expect { usage_obj.count(log) }.not_to change { usage_obj.ial2_users }
end

it 'returns a copy of itself with the updated usage metrics' do
log =
create_return_log(sp: integration, user: user, ial: 2, returned: Time.zone.today + 1.day)

expect(usage_obj.count(log)).to be_an_instance_of(described_class)
end
end

def create_return_log(sp:, user:, ial:, returned:)
create(
:sp_return_log,
issuer: sp.issuer,
user_id: user.id,
ial: ial,
requested_at: returned - 1.minute,
returned_at: returned,
)
end
end
Loading