Skip to content

Commit

Permalink
add validators to asset objects
Browse files Browse the repository at this point in the history
  • Loading branch information
mmenanno committed Nov 8, 2023
1 parent 62d3647 commit fca92db
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 6 deletions.
6 changes: 6 additions & 0 deletions lib/lunchmoney.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Module.include(T::Sig)

require_relative "lunchmoney/version"
require_relative "lunchmoney/validators"
require_relative "lunchmoney/api"

module LunchMoney
Expand All @@ -29,5 +30,10 @@ def configuration
@configuration = T.let(nil, T.nilable(LunchMoney::Configuration)) unless defined?(@configuration)
@configuration || LOCK.synchronize { @configuration = LunchMoney::Configuration.new }
end

sig { returns(T::Boolean) }
def validate_object_attributes?
configuration.validate_object_attributes
end
end
end
71 changes: 65 additions & 6 deletions lib/lunchmoney/assets/asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,52 @@ module LunchMoney
class Asset < LunchMoney::DataObject
# API object reference documentation: https://lunchmoney.dev/#assets-object

include LunchMoney::Validators

sig { returns(Integer) }
attr_accessor :id

sig { returns(String) }
attr_accessor :type_name, :name, :balance, :balance_as_of, :currency, :created_at
attr_reader :type_name, :balance_as_of, :created_at

sig { returns(T.nilable(String)) }
attr_reader :subtype_name

sig { returns(String) }
attr_accessor :name, :balance, :currency

sig { returns(T.nilable(String)) }
attr_accessor :subtype_name, :display_name, :closed_on, :institution_name
attr_accessor :display_name, :closed_on, :institution_name

sig { returns(T::Boolean) }
attr_accessor :exclude_transactions

VALID_TYPE_NAMES = T.let(
[
"cash",
"credit",
"investment",
"real estate",
"loan",
"vehicle",
"cryptocurrency",
"employee compensation",
"other liability",
"other asset",
],
T::Array[String],
)

VALID_SUBTYPE_NAMES = T.let(
[
"retirement",
"checking",
"savings",
"prepaid credit card",
],
T::Array[String],
)

sig do
params(
created_at: String,
Expand All @@ -36,18 +70,43 @@ class Asset < LunchMoney::DataObject
def initialize(created_at:, type_name:, name:, balance:, balance_as_of:, currency:, exclude_transactions:, id:,
subtype_name: nil, display_name: nil, closed_on: nil, institution_name: nil)
super()
@created_at = created_at
@type_name = type_name
@created_at = T.let(validate_iso8601!(created_at), String)
@type_name = T.let(validate_one_of!(type_name, VALID_TYPE_NAMES), String)
@name = name
@balance = balance
@balance_as_of = balance_as_of
@balance_as_of = T.let(validate_iso8601!(balance_as_of), String)
@currency = currency
@exclude_transactions = exclude_transactions
@id = id
@subtype_name = subtype_name
@subtype_name = T.let(
subtype_name.nil? ? subtype_name : validate_one_of!(subtype_name, VALID_SUBTYPE_NAMES),
T.nilable(String),
)
@display_name = display_name
@closed_on = closed_on
@institution_name = institution_name
end

sig { params(name: String).void }
def type_name=(name)
@type_name = validate_one_of!(name, VALID_TYPE_NAMES)
end

sig { params(name: T.nilable(String)).void }
def subtype_name=(name)
return unless name

@subtype_name = validate_one_of!(name, VALID_TYPE_NAMES)
end

sig { params(time: String).void }
def balance_as_of=(time)
@balance_as_of = validate_iso8601!(time)
end

sig { params(time: String).void }
def created_at=(time)
@created_at = validate_iso8601!(time)
end
end
end
4 changes: 4 additions & 0 deletions lib/lunchmoney/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ class Configuration
sig { returns(T.nilable(String)) }
attr_accessor :api_key

sig { returns(T::Boolean) }
attr_accessor :validate_object_attributes

sig { void }
def initialize
@api_key = ENV.fetch("LUNCHMONEY_TOKEN", nil)
@validate_object_attributes = T.let(true, T::Boolean)
end
end
end
2 changes: 2 additions & 0 deletions lib/lunchmoney/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ module LunchMoney
class Exception < StandardError; end

class InvalidApiKey < Exception; end

class InvalidObjectAttribute < Exception; end
end
42 changes: 42 additions & 0 deletions lib/lunchmoney/validators.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# typed: strict
# frozen_string_literal: true

require "time"

module LunchMoney
module Validators
include Kernel

sig { params(value: String, valid_values: T::Array[String]).returns(String) }
def validate_one_of!(value, valid_values)
return value unless LunchMoney.validate_object_attributes?

if valid_values.exclude?(value)
raise(InvalidObjectAttribute, "#{value} is invalid, must be one of #{valid_values.join(", ")}")
end

value
end

sig { params(value: String).returns(String) }
def validate_iso8601!(value)
return value unless LunchMoney.validate_object_attributes?

raise(InvalidObjectAttribute, "#{value} is not a valid ISO 8601 string") unless valid_iso8601_string?(value)

value
end

private

sig { params(time_string: String).returns(T::Boolean) }
def valid_iso8601_string?(time_string)
Time.iso8601(time_string)
true
rescue ArgumentError => error
raise unless error.message.match?("invalid xmlschema format")

false
end
end
end
21 changes: 21 additions & 0 deletions test/helpers/configuration_stubs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# typed: strict
# frozen_string_literal: true

module ConfigurationStubs
private

sig { void }
def should_validate_object_attributes
LunchMoney.configuration.expects(:validate_object_attributes).returns(true).at_least_once
end

sig { void }
def should_not_validate_object_attributes
LunchMoney.configuration.expects(:validate_object_attributes).returns(false).at_least_once
end

sig { void }
def remove_validate_object_attributes_expectation
LunchMoney.configuration.unstub(:validate_object_attributes)
end
end
90 changes: 90 additions & 0 deletions test/lunchmoney/assets/asset_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# typed: strict
# frozen_string_literal: true

require "test_helper"

class AssetTest < ActiveSupport::TestCase
test "type_name can be set to known valid types" do
LunchMoney::Asset::VALID_TYPE_NAMES.each do |type_name|
assert_nothing_raised do
create_asset(type_name:)
end
end
end

# test "type_name can not be set in an invalid type" do
# error = assert_raises(LunchMoney::InvalidObjectAttribute) do
# create_asset(type_name: "invalid_type_name")
# end

# assert_match(/is invalid, must be one of/, error.message)
# end

# test "subtype_name can be set to known valid types" do
# LunchMoney::Asset::VALID_SUBTYPE_NAMES.each do |subtype_name|
# assert_nothing_raised do
# create_asset(subtype_name:)
# end
# end
# end

# test "subtype_name can not be set in an invalid type" do
# error = assert_raises(LunchMoney::InvalidObjectAttribute) do
# create_asset(subtype_name: "invalid_subtype_name")
# end

# assert_match(/is invalid, must be one of/, error.message)
# end

# test "balance_as_of can be set to a valid timestamp" do
# assert_nothing_raised do
# create_asset(balance_as_of: "2023-01-01T01:01:01.000Z")
# end
# end

# test "balance_as_of can not be set to an invalid timestamp" do
# error = assert_raises(LunchMoney::InvalidObjectAttribute) do
# create_asset(balance_as_of: "2023-01-01")
# end

# assert_match(/is not a valid ISO 8601 string/, error.message)
# end

# test "created_at can be set to a valid timestamp" do
# assert_nothing_raised do
# create_asset(created_at: "2023-01-01T01:01:01.000Z")
# end
# end

# test "created_at can not be set to an invalid timestamp" do
# error = assert_raises(LunchMoney::InvalidObjectAttribute) do
# create_asset(created_at: "2023-01-01")
# end

# assert_match(/is not a valid ISO 8601 string/, error.message)
# end

sig do
params(
type_name: String,
subtype_name: String,
balance_as_of: String,
created_at: String,
).returns(LunchMoney::Asset)
end
def create_asset(type_name: "cash", subtype_name: "retirement", balance_as_of: "2023-01-01T01:01:01.000Z",
created_at: "2023-01-01T01:01:01.000Z")
LunchMoney::Asset.new(
"id": 1,
"type_name": type_name,
"subtype_name": subtype_name,
"name": "Test Asset 1",
"balance": "1201.0100",
"balance_as_of": balance_as_of,
"currency": "cad",
"institution_name": "Bank of Me",
"exclude_transactions": false,
"created_at": created_at,
)
end
end
75 changes: 75 additions & 0 deletions test/lunchmoney/validators_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# typed: strict
# frozen_string_literal: true

require "test_helper"

class ValidatorsTest < ActiveSupport::TestCase
include ConfigurationStubs
include LunchMoney::Validators

setup do
should_validate_object_attributes
end

test "validate_one_of validates values if validate_object_attributes is enabled" do
error = assert_raises(LunchMoney::InvalidObjectAttribute) do
validate_one_of!("bad_value", ["good_value"])
end

assert_match(/is invalid, must be one of/, error.message)
end

test "validate_one_of does not validate values if validate_object_attributes is disabled" do
remove_validate_object_attributes_expectation
should_not_validate_object_attributes

assert_nothing_raised do
validate_one_of!("bad_value", ["good_value"])
end
end

test "validate_one_of does not raise an error when set to a valid value" do
assert_nothing_raised do
validate_one_of!("good_value", ["good_value"])
end
end

test "validate_one_of raises an error when set to an invalid value" do
error = assert_raises(LunchMoney::InvalidObjectAttribute) do
validate_one_of!("bad_value", ["good_value"])
end

assert_match(/is invalid, must be one of/, error.message)
end

test "validate_iso8601 validates time if validate_object_attributes is enabled" do
error = assert_raises(LunchMoney::InvalidObjectAttribute) do
validate_iso8601!("2023-01-01")
end

assert_match(/is not a valid ISO 8601 string/, error.message)
end

test "validate_iso8601 does not validate values if validate_object_attributes is disabled" do
remove_validate_object_attributes_expectation
should_not_validate_object_attributes

assert_nothing_raised do
validate_iso8601!("2023-01-01")
end
end

test "validate_iso8601 does not raise an error when set to a valid value" do
assert_nothing_raised do
validate_iso8601!("2023-01-01T01:01:01.000Z")
end
end

test "validate_iso8601 raises an error when set to an invalid value" do
error = assert_raises(LunchMoney::InvalidObjectAttribute) do
validate_iso8601!("2023-01-01")
end

assert_match(/is not a valid ISO 8601 string/, error.message)
end
end
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require "mocha/minitest"
require "pry"

require_relative "helpers/configuration_stubs"
require_relative "helpers/mocha_typed"
require_relative "helpers/mock_response_helper"
require_relative "helpers/fake_response_data_helper"

0 comments on commit fca92db

Please sign in to comment.