diff --git a/Gemfile.lock b/Gemfile.lock index a8d6434e6..3285380f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,7 +186,7 @@ GEM thor (~> 0.14) globalid (0.4.1) activesupport (>= 4.2.0) - grape (1.0.3) + grape (1.1.0) activesupport builder mustermann-grape (~> 1.0.0) diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb new file mode 100644 index 000000000..de5ef9dcf --- /dev/null +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -0,0 +1,57 @@ +module Api + module V1 + module Registrant + class ContactsController < BaseController + before_action :set_contacts_pool + + def index + limit = params[:limit] || 200 + offset = params[:offset] || 0 + + if limit.to_i > 200 || limit.to_i < 1 + render(json: { errors: [{ limit: ['parameter is out of range'] }] }, + status: :bad_request) && return + end + + if offset.to_i.negative? + render(json: { errors: [{ offset: ['parameter is out of range'] }] }, + status: :bad_request) && return + end + + @contacts = @contacts_pool.limit(limit).offset(offset) + render json: @contacts + end + + def show + @contact = @contacts_pool.find_by(uuid: params[:uuid]) + + if @contact + render json: @contact + else + render json: { errors: [{ base: ['Contact not found'] }] }, status: :not_found + end + end + + private + + def set_contacts_pool + country_code, ident = current_user.registrant_ident.to_s.split '-' + associated_domain_ids = begin + BusinessRegistryCache.fetch_by_ident_and_cc(ident, country_code).associated_domain_ids + end + + available_contacts_ids = begin + DomainContact.where(domain_id: associated_domain_ids).pluck(:contact_id) | + Domain.where(id: associated_domain_ids).pluck(:registrant_id) + end + + @contacts_pool = Contact.where(id: available_contacts_ids) + rescue Soap::Arireg::NotAvailableError => error + Rails.logger.fatal("[EXCEPTION] #{error}") + render json: { errors: [{ base: ['Business Registry not available'] }] }, + status: :service_unavailable and return + end + end + end + end +end diff --git a/app/controllers/api/v1/registrant/domains_controller.rb b/app/controllers/api/v1/registrant/domains_controller.rb index 27b7b6125..7209f8a10 100644 --- a/app/controllers/api/v1/registrant/domains_controller.rb +++ b/app/controllers/api/v1/registrant/domains_controller.rb @@ -1,5 +1,3 @@ -require 'rails5_api_controller_backport' - module Api module V1 module Registrant diff --git a/app/controllers/registrant/contacts_controller.rb b/app/controllers/registrant/contacts_controller.rb index f73650de2..267b4d68d 100644 --- a/app/controllers/registrant/contacts_controller.rb +++ b/app/controllers/registrant/contacts_controller.rb @@ -26,4 +26,4 @@ class Registrant::ContactsController < RegistrantController BusinessRegistryCache.fetch_by_ident_and_cc(ident, ident_cc).associated_domain_ids end end -end \ No newline at end of file +end diff --git a/app/controllers/registrar/deposits_controller.rb b/app/controllers/registrar/deposits_controller.rb index ec6d13977..818e38c6d 100644 --- a/app/controllers/registrar/deposits_controller.rb +++ b/app/controllers/registrar/deposits_controller.rb @@ -10,12 +10,12 @@ class Registrar @deposit = Deposit.new(deposit_params.merge(registrar: current_user.registrar)) @invoice = @deposit.issue_prepayment_invoice - if @invoice&.persisted? + if @invoice flash[:notice] = t(:please_pay_the_following_invoice) redirect_to [:registrar, @invoice] else - flash.now[:alert] = t(:failed_to_create_record) - render 'new' + flash[:alert] = @deposit.errors.full_messages.join(', ') + redirect_to new_registrar_deposit_path end end diff --git a/app/models/deposit.rb b/app/models/deposit.rb index 23045196a..2eff26bcc 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -4,13 +4,17 @@ class Deposit extend ActiveModel::Naming include DisableHtml5Validation - attr_accessor :amount, :description, :registrar, :registrar_id + attr_accessor :description, :registrar, :registrar_id + attr_writer :amount validates :amount, :registrar, presence: true validate :validate_amount + def validate_amount - return if BigDecimal.new(amount) >= Setting.minimum_deposit - errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: Setting.minimum_deposit, currency: 'EUR')) + minimum_allowed_amount = [0.01, Setting.minimum_deposit].max + return if amount >= minimum_allowed_amount + errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: minimum_allowed_amount, + currency: 'EUR')) end def initialize(attributes = {}) @@ -24,10 +28,12 @@ class Deposit end def amount - BigDecimal.new(@amount.to_s.sub(/,/, '.')) + return BigDecimal('0.0') if @amount.blank? + BigDecimal(@amount.to_s.tr(',', '.'), 10) end def issue_prepayment_invoice - valid? && registrar.issue_prepayment_invoice(amount, description) + return unless valid? + registrar.issue_prepayment_invoice(amount, description) end end diff --git a/config/routes.rb b/config/routes.rb index c3545706a..20c55f25a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,7 +23,8 @@ Rails.application.routes.draw do namespace :registrant do post 'auth/eid', to: 'auth#eid' - resources :domains, only: [:index, :show], param: :uuid + resources :domains, only: %i[index show], param: :uuid + resources :contacts, only: %i[index show], param: :uuid end end end diff --git a/doc/registrant-api.md b/doc/registrant-api.md new file mode 100644 index 000000000..c6f063a91 --- /dev/null +++ b/doc/registrant-api.md @@ -0,0 +1,11 @@ +# Registrant API integration specification + +Test API endpoint: TBA +Production API endpoint: TBA + +Main communication specification through Registrant API: + +[Authentication](registrant-api/v1/authentication.md) +[Domains](registrant-api/v1/domain.md) +[Domain Lock](registrant-api/v1/domain_lock.md) +[Contacts](registrant-api/v1/contact.md) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md new file mode 100644 index 000000000..20ad6edd6 --- /dev/null +++ b/doc/registrant-api/v1/authentication.md @@ -0,0 +1,109 @@ +# Authentication + +## Authenticating with mobileID or ID-card + +For specified partners the API allows for use of data from mobile ID for +authentication. API client should perform authentication with eID according to +the approriate documentation, and then pass on values from the webserver's +certificate to the API server. + +## POST /api/v1/registrant/auth/eid + +Returns a bearer token to be used for further API requests. Tokens are valid for 2 hours since their creation. + +#### Paramaters + +Values in brackets represent values that come from the id card certificate. + +| Field name | Required | Type | Allowed values | Description | +| ----------------- | -------- | ---- | -------------- | ----------- | +| ident | true | String | | Identity code of the user (`serialNumber`) | +| first_name | true | String | | Name of the customer (`GN`) | +| last_name | true | String | | Name of the customer (`SN`) | + + +#### Request +``` +POST /api/v1/auth/token HTTP/1.1 +Accept: application/json +Content-type: application/json + +{ + "ident": "30110100103", + "first_name": "Jan", + "last_name": "Tamm", +} +``` + +#### Response +``` +HTTP/1.1 201 +Content-Type: application.json + + +{ + "access_token": "", + "expires_at": "2018-07-13 11:30:51 UTC", + "type": "Bearer" +} +``` + +## POST /api/v1/auth/username -- NOT IMPLEMENTED + +#### Paramaters + +Values in brackets represent values that come from the id card certificate + +| Field name | Required | Type | Allowed values | Description | +| ----------------- | -------- | ---- | -------------- | ----------- | +| username | true | String | Username as provided by the user | | +| password | true | String | Password as provided by the user | | + + +#### Request +``` +POST /api/v1/auth/token HTTP/1.1 +Accept: application/json +Content-type: application/json +``` + +#### Response +``` +HTTP/1.1 201 +Content-Type: application.json + + +{ + "access_token": "", + "expires_at": "2018-07-13 11:30:51 UTC", + "type": "Bearer" +} +``` + +## Implementation notes: + +We do not need to store the session data at all, instead we can levarage AES encryption and use +Rails secret as the key. General approximation: + +```ruby +class AuthenticationToken + def initialize(secret = Rails.application.config.secret_key_base, values = {}) + end + + def create_token_hash + data = values.to_s + + cipher = OpenSSL::Cipher::AES.new(256, :CBC) + cipher.encrypt + + encrypted = cipher.update(data) + cipher.final + base64_encoded = Base64.encode64(encrypted) + + { + token: base64_encoded, + expires_in = values[:expires_in] + type: "Bearer" + } + end +end +``` diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md new file mode 100644 index 000000000..4e1c12bec --- /dev/null +++ b/doc/registrant-api/v1/contact.md @@ -0,0 +1,194 @@ +## GET /api/v1/registrant/contacts +Returns contacts of the current registrar. + + +#### Parameters + +| Field name | Required | Type | Allowed values | Description | +| ---------- | -------- | ---- | -------------- | ----------- | +| limit | false | Integer | [1..200] | How many contacts to show | +| offset | false | Integer | | Contact number to start at | + +#### Request +``` +GET /api/v1/registrant/contacts?limit=1 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "contacts": [ + { + "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", + "domain_names": ["example.com"], + "code": "REGISTRAR2:SH022086480", + "phone": "+372.12345678", + "email": "hoyt@deckowbechtelar.net", + "fax": null, + "created_at": "2015-09-09T09:11:14.130Z", + "updated_at": "2015-09-09T09:11:14.130Z", + "ident": "37605030299", + "ident_type": "priv", + "auth_info": "password", + "name": "Karson Kessler0", + "org_name": null, + "registrar_id": 2, + "creator_str": null, + "updator_str": null, + "ident_country_code": "EE", + "city": "Tallinn", + "street": "Short street 11", + "zip": "11111", + "country_code": "EE", + "state": null, + "legacy_id": null, + "statuses": [ + "ok" + ], + "status_notes": { + } + } + ], + "total_number_of_records": 2 +} +``` + +## GET /api/v1/registrant/contacts/$UUID +Returns contacts of the current registrar. + + +#### Request +``` +GET /api/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", + "domain_names": ["example.com"], + "code": "REGISTRAR2:SH022086480", + "phone": "+372.12345678", + "email": "hoyt@deckowbechtelar.net", + "fax": null, + "created_at": "2015-09-09T09:11:14.130Z", + "updated_at": "2015-09-09T09:11:14.130Z", + "ident": "37605030299", + "ident_type": "priv", + "auth_info": "password", + "name": "Karson Kessler0", + "org_name": null, + "registrar_id": 2, + "creator_str": null, + "updator_str": null, + "ident_country_code": "EE", + "city": "Tallinn", + "street": "Short street 11", + "zip": "11111", + "country_code": "EE", + "state": null, + "legacy_id": null, + "statuses": [ + "ok" + ], + "status_notes": {} +} +``` + +## PATCH /api/v1/registrant/contacts/$UUID + +Update contact details for a contact. + +#### Parameters + +| Field name | Required | Type | Allowed values | Description | +| ---- | --- | --- | --- | --- | +| email | false | String | | New email address | +| phone | false | String | | New phone number | +| fax | false | String | | New fax number | +| city | false | String | | New city name | +| street | false | String | | New street name | +| zip | false | String | | New zip code | +| country_code | false | String | | New country code in 2 letter format ('EE', 'LV') | +| state | false | String | | New state name | + + +#### Request +``` +PATCH /api/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1 +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Accept: application/json +Content-type: application/json + +{ + "email": "foo@bar.baz", + "phone": "+372.12345671", + "fax": "+372.12345672", + "city": "New City", + "street": "Main Street 123", + "zip": "22222", + "country_code": "LV", + "state": "New state" +} + +``` +#### Response on success + +``` +HTTP/1.1 200 +Content-Type: application.json + +{ + "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", + "domain_names": ["example.com"], + "code": "REGISTRAR2:SH022086480", + "phone": "+372.12345671", + "email": "foo@bar.baz", + "fax": "+372.12345672", + "created_at": "2015-09-09T09:11:14.130Z", + "updated_at": "2018-09-09T09:11:14.130Z", + "ident": "37605030299", + "ident_type": "priv", + "auth_info": "password", + "name": "Karson Kessler0", + "org_name": null, + "registrar_id": 2, + "creator_str": null, + "updator_str": null, + "ident_country_code": "EE", + "city": "New City", + "street": "Main Street 123", + "zip": "22222", + "country_code": "LV", + "state": "New state" + "legacy_id": null, + "statuses": [ + "ok" + ], + "status_notes": {} +} +``` + +### Response on failure +``` +HTTP/1.1 400 +Content-Type: application.json + +{ + "errors": [ + { "phone": "Phone nr is invalid" } + ] +} +``` diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md new file mode 100644 index 000000000..09495220d --- /dev/null +++ b/doc/registrant-api/v1/domain.md @@ -0,0 +1,161 @@ +# Domain related actions + +## GET /api/v1/registrant/domains + +Returns domains of the current registrant. + + +#### Parameters + +| Field name | Required | Type | Allowed values | Description | +| ---------- | -------- | ---- | -------------- | ----------- | +| limit | false | Integer | [1..200] | How many domains to show | +| offset | false | Integer | | Domain number to start at | +| details | false | String | ["true", "false"] | Whether to include details | + +#### Request +``` +GET api/v1/registrant/domains?limit=1&details=true HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "domains": [ + { + "uuid": "98d1083a-8863-4153-93e4-caee4a013535", + "name": "domain0.ee", + "registrar_id": 2, + "registered_at": "2015-09-09T09:11:14.861Z", + "status": null, + "valid_from": "2015-09-09T09:11:14.861Z", + "valid_to": "2016-09-09T09:11:14.861Z", + "registrant_id": 1, + "transfer_code": "98oiewslkfkd", + "created_at": "2015-09-09T09:11:14.861Z", + "updated_at": "2015-09-09T09:11:14.860Z", + "name_dirty": "domain0.ee", + "name_puny": "domain0.ee", + "period": 1, + "period_unit": "y", + "creator_str": null, + "updator_str": null, + "legacy_id": null, + "legacy_registrar_id": null, + "legacy_registrant_id": null, + "outzone_at": "2016-09-24T09:11:14.861Z", + "delete_at": "2016-10-24T09:11:14.861Z", + "registrant_verification_asked_at": null, + "registrant_verification_token": null, + "pending_json": { + }, + "force_delete_at": null, + "statuses": [ + "ok" + ], + "reserved": false, + "status_notes": { + }, + "statuses_backup": [ + ] + } + ], + "total_number_of_records": 2 +} +``` + +## GET api/v1/registrant/domains + +Returns domain names with offset. + + +#### Request +``` +GET api/v1/registrant/domains?offset=1 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "domains": [ + "domain1.ee" + ], + "total_number_of_records": 2 +} +``` + +## GET api/v1/registrant/domains/$UUID + +Returns a single domain object. + + +#### Request +``` +GET api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response for success + +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "uuid": "98d1083a-8863-4153-93e4-caee4a013535", + "name": "domain0.ee", + "registrar_id": 2, + "registered_at": "2015-09-09T09:11:14.861Z", + "status": null, + "valid_from": "2015-09-09T09:11:14.861Z", + "valid_to": "2016-09-09T09:11:14.861Z", + "registrant_id": 1, + "transfer_code": "98oiewslkfkd", + "created_at": "2015-09-09T09:11:14.861Z", + "updated_at": "2015-09-09T09:11:14.860Z", + "name_dirty": "domain0.ee", + "name_puny": "domain0.ee", + "period": 1, + "period_unit": "y", + "creator_str": null, + "updator_str": null, + "legacy_id": null, + "legacy_registrar_id": null, + "legacy_registrant_id": null, + "outzone_at": "2016-09-24T09:11:14.861Z", + "delete_at": "2016-10-24T09:11:14.861Z", + "registrant_verification_asked_at": null, + "registrant_verification_token": null, + "pending_json": {}, + "force_delete_at": null, + "statuses": [ + "ok" + ], + "reserved": false, + "status_notes": {}, + "statuses_backup": [] +} +``` + +#### Response for failure + +``` +HTTP/1.1 404 +Content-Type: application/json + +{ "errors": ["Domain not found"] } +``` diff --git a/doc/registrant-api/v1/domain_lock.md b/doc/registrant-api/v1/domain_lock.md new file mode 100644 index 000000000..5f275edb4 --- /dev/null +++ b/doc/registrant-api/v1/domain_lock.md @@ -0,0 +1,163 @@ +# Domain locks + +## POST api/v1/registrant/domains/$UUID/registry_lock + +Set a registry lock on a domain. + +#### Request +``` +POST api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535/registry_lock HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response for success + +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "uuid": "98d1083a-8863-4153-93e4-caee4a013535", + "name": "domain0.ee", + "registrar_id": 2, + "registered_at": "2015-09-09T09:11:14.861Z", + "status": null, + "valid_from": "2015-09-09T09:11:14.861Z", + "valid_to": "2016-09-09T09:11:14.861Z", + "registrant_id": 1, + "transfer_code": "98oiewslkfkd", + "created_at": "2015-09-09T09:11:14.861Z", + "updated_at": "2015-09-09T09:11:14.860Z", + "name_dirty": "domain0.ee", + "name_puny": "domain0.ee", + "period": 1, + "period_unit": "y", + "creator_str": null, + "updator_str": null, + "legacy_id": null, + "legacy_registrar_id": null, + "legacy_registrant_id": null, + "outzone_at": "2016-09-24T09:11:14.861Z", + "delete_at": "2016-10-24T09:11:14.861Z", + "registrant_verification_asked_at": null, + "registrant_verification_token": null, + "pending_json": {}, + "force_delete_at": null, + "statuses": [ + "serverUpdateProhibited", + "serverDeleteProhibited", + "serverTransferProhibited" + ], + "reserved": false, + "status_notes": {}, + "statuses_backup": [] +} +``` + +#### Response for failure + +``` +HTTP/1.1 400 +Content-Type: application/json + +{ + "errors": [ + { "base": "domain cannot be locked" } + ] +} + +``` + +``` +HTTP/1.1 404 +Content-Type: application/json + +{ + "errors": [ + { "base": "domain does not exist" } + ] +} + +``` + +## DELETE api/v1/registrant/domains/$UUID/registry_lock + +Remove a registry lock. + +#### Request +``` +DELETE api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535/registry_lock HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response for success + +``` +HTTP/1.1 200 +Content-Type: application/json + +{ + "uuid": "98d1083a-8863-4153-93e4-caee4a013535", + "name": "domain0.ee", + "registrar_id": 2, + "registered_at": "2015-09-09T09:11:14.861Z", + "status": null, + "valid_from": "2015-09-09T09:11:14.861Z", + "valid_to": "2016-09-09T09:11:14.861Z", + "registrant_id": 1, + "transfer_code": "98oiewslkfkd", + "created_at": "2015-09-09T09:11:14.861Z", + "updated_at": "2015-09-09T09:11:14.860Z", + "name_dirty": "domain0.ee", + "name_puny": "domain0.ee", + "period": 1, + "period_unit": "y", + "creator_str": null, + "updator_str": null, + "legacy_id": null, + "legacy_registrar_id": null, + "legacy_registrant_id": null, + "outzone_at": "2016-09-24T09:11:14.861Z", + "delete_at": "2016-10-24T09:11:14.861Z", + "registrant_verification_asked_at": null, + "registrant_verification_token": null, + "pending_json": {}, + "force_delete_at": null, + "statuses": [ + "ok" + ], + "reserved": false, + "status_notes": {}, + "statuses_backup": [] +} +``` + +#### Response for failure + +``` +HTTP/1.1 400 +Content-Type: application/json + +{ + "errors": [ + { "base": "domain cannot be unlocked" } + ] +} + +``` + +``` +HTTP/1.1 404 +Content-Type: application/json + +{ + "errors": [ + { "base": "domain does not exist" } + ] +} + +``` diff --git a/spec/models/deposit_spec.rb b/spec/models/deposit_spec.rb deleted file mode 100644 index ff77dbd98..000000000 --- a/spec/models/deposit_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'rails_helper' - -describe Deposit do - context 'with invalid attribute' do - before :all do - @deposit = Deposit.new - end - - it 'should not be valid' do - @deposit.valid? - @deposit.errors.full_messages.should match_array([ - "Registrar is missing" - ]) - end - - it 'should have 0 amount' do - @deposit.amount.should == 0 - end - - it 'should not be presisted' do - @deposit.persisted?.should == false - end - - it 'should replace comma with point for 0' do - @deposit.amount = '0,0' - @deposit.amount.should == 0.0 - end - - it 'should replace comma with points' do - @deposit.amount = '10,11' - @deposit.amount.should == 10.11 - end - - it 'should work with float as well' do - @deposit.amount = 0.123 - @deposit.amount.should == 0.123 - end - end -end diff --git a/test/integration/api/registrant/registrant_api_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb new file mode 100644 index 000000000..ddeaee9f3 --- /dev/null +++ b/test/integration/api/registrant/registrant_api_contacts_test.rb @@ -0,0 +1,117 @@ +require 'test_helper' +require 'auth_token/auth_token_creator' + +class RegistrantApiContactsTest < ApplicationIntegrationTest + def setup + super + + @original_registry_time = Setting.days_to_keep_business_registry_cache + Setting.days_to_keep_business_registry_cache = 1 + travel_to Time.zone.parse('2010-07-05') + + @user = users(:registrant) + @auth_headers = { 'HTTP_AUTHORIZATION' => auth_token } + end + + def teardown + super + + Setting.days_to_keep_business_registry_cache = @original_registry_time + travel_back + end + + def test_root_returns_domain_list + get '/api/v1/registrant/contacts', {}, @auth_headers + assert_equal(200, response.status) + + json_body = JSON.parse(response.body, symbolize_names: true) + assert_equal(5, json_body.count) + array_of_contact_codes = json_body.map { |x| x[:code] } + assert(array_of_contact_codes.include?('william-001')) + assert(array_of_contact_codes.include?('jane-001')) + end + + def test_root_accepts_limit_and_offset_parameters + get '/api/v1/registrant/contacts', { 'limit' => 1, 'offset' => 0 }, @auth_headers + response_json = JSON.parse(response.body, symbolize_names: true) + assert_equal(200, response.status) + assert_equal(1, response_json.count) + + get '/api/v1/registrant/contacts', {}, @auth_headers + response_json = JSON.parse(response.body, symbolize_names: true) + assert_equal(5, response_json.count) + end + + def test_get_contact_details_by_uuid + get '/api/v1/registrant/contacts/0aa54704-d6f7-4ca9-b8ca-2827d9a4e4eb', {}, @auth_headers + assert_equal(200, response.status) + + contact = JSON.parse(response.body, symbolize_names: true) + assert_equal('william@inbox.test', contact[:email]) + end + + def test_root_returns_503_when_business_registry_is_not_available + raise_not_available = -> (a, b) { raise Soap::Arireg::NotAvailableError.new({}) } + BusinessRegistryCache.stub :fetch_by_ident_and_cc, raise_not_available do + get '/api/v1/registrant/contacts', {}, @auth_headers + + assert_equal(503, response.status) + response_json = JSON.parse(response.body, symbolize_names: true) + assert_equal({ errors: [base: ['Business Registry not available']] }, response_json) + end + end + + def test_get_contact_details_by_uuid_returns_404_for_non_existent_contact + get '/api/v1/registrant/contacts/nonexistent-uuid', {}, @auth_headers + assert_equal(404, response.status) + + response_json = JSON.parse(response.body, symbolize_names: true) + assert_equal({ errors: [{ base: ['Contact not found'] }] }, response_json) + end + + def test_root_does_not_accept_limit_higher_than_200 + get '/api/v1/registrant/contacts', { 'limit' => 400, 'offset' => 0 }, @auth_headers + assert_equal(400, response.status) + response_json = JSON.parse(response.body, symbolize_names: true) + assert_equal({ errors: [{ limit: ['parameter is out of range'] }] }, response_json) + end + + def test_root_does_not_accept_offset_lower_than_0 + get '/api/v1/registrant/contacts', { 'limit' => 200, 'offset' => "-10" }, @auth_headers + assert_equal(400, response.status) + response_json = JSON.parse(response.body, symbolize_names: true) + assert_equal({ errors: [{ offset: ['parameter is out of range'] }] }, response_json) + end + + def test_root_returns_401_without_authorization + get '/api/v1/registrant/contacts', {}, {} + assert_equal(401, response.status) + json_body = JSON.parse(response.body, symbolize_names: true) + + assert_equal({ errors: [base: ['Not authorized']] }, json_body) + end + + def test_details_returns_401_without_authorization + get '/api/v1/registrant/contacts/c0a191d5-3793-4f0b-8f85-491612d0293e', {}, {} + assert_equal(401, response.status) + json_body = JSON.parse(response.body, symbolize_names: true) + + assert_equal({ errors: [base: ['Not authorized']] }, json_body) + end + + def test_details_returns_404_for_non_existent_contact + get '/api/v1/registrant/contacts/some-random-uuid', {}, @auth_headers + assert_equal(404, response.status) + json_body = JSON.parse(response.body, symbolize_names: true) + + assert_equal({ errors: [base: ['Contact not found']] }, json_body) + end + + private + + def auth_token + token_creator = AuthTokenCreator.create_with_defaults(@user) + hash = token_creator.token_in_hash + "Bearer #{hash[:access_token]}" + end +end diff --git a/test/models/deposit_test.rb b/test/models/deposit_test.rb new file mode 100644 index 000000000..b7510b960 --- /dev/null +++ b/test/models/deposit_test.rb @@ -0,0 +1,59 @@ +require 'test_helper' + +class DepositTest < ActiveSupport::TestCase + def setup + super + + @deposit = Deposit.new(registrar: registrars(:bestnames)) + @minimum_deposit = Setting.minimum_deposit + Setting.minimum_deposit = 1.00 + end + + def teardown + super + + Setting.minimum_deposit = @minimum_deposit + end + + def test_validate_amount_cannot_be_lower_than_0_01 + Setting.minimum_deposit = 0.0 + @deposit.amount = -10 + refute(@deposit.valid?) + assert(@deposit.errors.full_messages.include?("Amount is too small. Minimum deposit is 0.01 EUR")) + end + + def test_validate_amount_cannot_be_lower_than_minimum_deposit + @deposit.amount = 0.10 + refute(@deposit.valid?) + + assert(@deposit.errors.full_messages.include?("Amount is too small. Minimum deposit is 1.0 EUR")) + end + + def test_registrar_must_be_set + deposit = Deposit.new(amount: 120) + refute(deposit.valid?) + + assert(deposit.errors.full_messages.include?("Registrar is missing")) + end + + def test_amount_is_converted_from_string + @deposit.amount = "12.00" + assert_equal(BigDecimal.new("12.00"), @deposit.amount) + + @deposit.amount = "12,11" + assert_equal(BigDecimal.new("12.11"), @deposit.amount) + end + + def test_amount_is_converted_from_float + @deposit.amount = 12.0044 + assert_equal(BigDecimal.new("12.0044"), @deposit.amount) + + @deposit.amount = 12.0144 + assert_equal(BigDecimal.new("12.0144"), @deposit.amount) + end + + def test_amount_is_converted_from_nil + @deposit.amount = nil + assert_equal(BigDecimal.new("0.00"), @deposit.amount) + end +end diff --git a/test/system/registrar_area/invoices/new_test.rb b/test/system/registrar_area/invoices/new_test.rb index b9b6b6db4..a5a72fbe8 100644 --- a/test/system/registrar_area/invoices/new_test.rb +++ b/test/system/registrar_area/invoices/new_test.rb @@ -29,11 +29,10 @@ class NewInvoiceTest < ApplicationSystemTestCase assert_text 'Pay invoice' end - # This test case should fail once issue #651 gets fixed - def test_create_new_invoice_with_amount_0_goes_through + def test_create_new_invoice_with_comma_in_number visit registrar_invoices_path click_link_or_button 'Add deposit' - fill_in 'Amount', with: '0.00' + fill_in 'Amount', with: '200,00' fill_in 'Description', with: 'My first invoice' assert_difference 'Invoice.count', 1 do @@ -42,7 +41,33 @@ class NewInvoiceTest < ApplicationSystemTestCase assert_text 'Please pay the following invoice' assert_text 'Invoice no. 131050' - assert_text 'Subtotal 0,00 €' + assert_text 'Subtotal 200,00 €' assert_text 'Pay invoice' end + + def test_create_new_invoice_fails_when_amount_is_0 + visit registrar_invoices_path + click_link_or_button 'Add deposit' + fill_in 'Amount', with: '0.00' + fill_in 'Description', with: 'My first invoice' + + assert_no_difference 'Invoice.count' do + click_link_or_button 'Add' + end + + assert_text 'Amount is too small. Minimum deposit is 0.01 EUR' + end + + def test_create_new_invoice_fails_when_amount_is_negative + visit registrar_invoices_path + click_link_or_button 'Add deposit' + fill_in 'Amount', with: '-120.00' + fill_in 'Description', with: 'My first invoice' + + assert_no_difference 'Invoice.count' do + click_link_or_button 'Add' + end + + assert_text 'Amount is too small. Minimum deposit is 0.01 EUR' + end end