currency-converter 0.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89e58c796f1088778eb0d65eeba3ae7cb2b262eaeefb19061502c6be554cb841
4
- data.tar.gz: 7297e8ba1271a95783dd23678d317bb2daf1c06089f21de130f99eeeb32b4bb6
3
+ metadata.gz: 41b3778d46388a2a405491288e53299f646d91033c38905f1ff9b8b2a6022ecc
4
+ data.tar.gz: ea8d42edb7f824133e28abb514022a5aa217e1e8df877193bd310864baa175f9
5
5
  SHA512:
6
- metadata.gz: 70ffe563aa7df2555f266b17ba41c2a32a1ba6f5e56b80d1b00cdc45c35451b82571566760265c36526e62066fdc03a614108940f16f1fae699e9948bd42aa63
7
- data.tar.gz: 1eb14c78cd92a47145981502cd7c8b89de1500bce13ee2dfffbfea75142c5f06c743598452fb769ef5558f40542d32f1e72ed9a75e500b89bb8b74f062e2c033
6
+ metadata.gz: e7eb0dbca550cb87b40e2dfad8c071371cdd29a82ba4f306fd8b063494f3d53e00f0023787d3525cd0d09195394bc2844fb8ff45028c32419997129dcb729747
7
+ data.tar.gz: 8332235cd209a3121077cd751b383a8505f46658d7e2ff2413238fedd9ae374a277ae423ddec37488ea83e60ecb9f6c9beed3c7cd9e1650ed0c00e477bfb9f28
@@ -0,0 +1,27 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ pull_request:
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ name: Ruby ${{ matrix.ruby }}
14
+ strategy:
15
+ matrix:
16
+ ruby:
17
+ - '3.0.4'
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: true
26
+ - name: Run the default task
27
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # Claude Code documentation (local only)
14
+ CLAUDE.md
15
+ ACTION_PLAN.md
16
+ TESTING_PLAN.md
17
+ TESTING_SUMMARY.md
data/.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
@@ -0,0 +1,76 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="ModuleRunConfigurationManager">
4
+ <shared />
5
+ </component>
6
+ <component name="NewModuleRootManager">
7
+ <content url="file://$MODULE_DIR$">
8
+ <sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
9
+ <sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
10
+ <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
11
+ </content>
12
+ <orderEntry type="inheritedJdk" />
13
+ <orderEntry type="sourceFolder" forTests="false" />
14
+ <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, rbenv: 3.0.5) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.33, rbenv: 3.0.5) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.1, rbenv: 3.0.5) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="json (v2.5.1, rbenv: 3.0.5) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="language_server-protocol (v3.17.0.3, rbenv: 3.0.5) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="parallel (v1.26.3, rbenv: 3.0.5) [gem]" level="application" />
20
+ <orderEntry type="library" scope="PROVIDED" name="parser (v3.3.6.0, rbenv: 3.0.5) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="racc (v1.5.2, rbenv: 3.0.5) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, rbenv: 3.0.5) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.3, rbenv: 3.0.5) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.9.2, rbenv: 3.0.5) [gem]" level="application" />
25
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v3.13.0, rbenv: 3.0.5) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.13.2, rbenv: 3.0.5) [gem]" level="application" />
27
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.13.3, rbenv: 3.0.5) [gem]" level="application" />
28
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.13.2, rbenv: 3.0.5) [gem]" level="application" />
29
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.13.1, rbenv: 3.0.5) [gem]" level="application" />
30
+ <orderEntry type="library" scope="PROVIDED" name="rubocop (v1.68.0, rbenv: 3.0.5) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.34.1, rbenv: 3.0.5) [gem]" level="application" />
32
+ <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.13.0, rbenv: 3.0.5) [gem]" level="application" />
33
+ <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.6.0, rbenv: 3.0.5) [gem]" level="application" />
34
+ </component>
35
+ <component name="RakeTasksCache-v2">
36
+ <option name="myRootTask">
37
+ <RakeTaskImpl id="rake">
38
+ <subtasks>
39
+ <RakeTaskImpl description="Build currency_converter-0.1.0.gem into the pkg directory" fullCommand="build" id="build" />
40
+ <RakeTaskImpl id="build">
41
+ <subtasks>
42
+ <RakeTaskImpl description="Generate SHA512 checksum if currency_converter-0.1.0.gem into the checksums directory" fullCommand="build:checksum" id="checksum" />
43
+ </subtasks>
44
+ </RakeTaskImpl>
45
+ <RakeTaskImpl description="Remove any temporary products" fullCommand="clean" id="clean" />
46
+ <RakeTaskImpl description="Remove any generated files" fullCommand="clobber" id="clobber" />
47
+ <RakeTaskImpl description="Build and install currency_converter-0.1.0.gem into system gems" fullCommand="install" id="install" />
48
+ <RakeTaskImpl id="install">
49
+ <subtasks>
50
+ <RakeTaskImpl description="Build and install currency_converter-0.1.0.gem into system gems without network access" fullCommand="install:local" id="local" />
51
+ </subtasks>
52
+ </RakeTaskImpl>
53
+ <RakeTaskImpl description="Create tag v0.1.0 and build and push currency_converter-0.1.0.gem to TODO: Set to your gem server 'https://example.com'" fullCommand="release[remote]" id="release[remote]" />
54
+ <RakeTaskImpl description="Run RuboCop" fullCommand="rubocop" id="rubocop" />
55
+ <RakeTaskImpl id="rubocop">
56
+ <subtasks>
57
+ <RakeTaskImpl description="Autocorrect RuboCop offenses (only when it's safe)" fullCommand="rubocop:autocorrect" id="autocorrect" />
58
+ <RakeTaskImpl description="Autocorrect RuboCop offenses (safe and unsafe)" fullCommand="rubocop:autocorrect_all" id="autocorrect_all" />
59
+ <RakeTaskImpl description="" fullCommand="rubocop:auto_correct" id="auto_correct" />
60
+ </subtasks>
61
+ </RakeTaskImpl>
62
+ <RakeTaskImpl description="Run RSpec code examples" fullCommand="spec" id="spec" />
63
+ <RakeTaskImpl description="" fullCommand="default" id="default" />
64
+ <RakeTaskImpl description="" fullCommand="release" id="release" />
65
+ <RakeTaskImpl id="release">
66
+ <subtasks>
67
+ <RakeTaskImpl description="" fullCommand="release:guard_clean" id="guard_clean" />
68
+ <RakeTaskImpl description="" fullCommand="release:rubygem_push" id="rubygem_push" />
69
+ <RakeTaskImpl description="" fullCommand="release:source_control_push" id="source_control_push" />
70
+ </subtasks>
71
+ </RakeTaskImpl>
72
+ </subtasks>
73
+ </RakeTaskImpl>
74
+ </option>
75
+ </component>
76
+ </module>
data/.idea/misc.xml ADDED
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectRootManager" version="2" project-jdk-name="rbenv: 3.0.5" project-jdk-type="RUBY_SDK" />
4
+ </project>
data/.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/currency_converter.iml" filepath="$PROJECT_DIR$/.idea/currency_converter.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
data/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
data/.rubocop.yml CHANGED
@@ -1,5 +1,7 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.6
3
+ NewCops: enable
4
+ SuggestExtensions: false
3
5
 
4
6
  Style/StringLiterals:
5
7
  Enabled: true
@@ -11,3 +13,19 @@ Style/StringLiteralsInInterpolation:
11
13
 
12
14
  Layout/LineLength:
13
15
  Max: 120
16
+
17
+ # Allow longer methods for complex logic
18
+ Metrics/MethodLength:
19
+ Max: 15
20
+ Exclude:
21
+ - 'lib/currency_converter/api_client.rb'
22
+ - 'lib/currency_converter/converter.rb'
23
+
24
+ # Allow longer blocks in tests
25
+ Metrics/BlockLength:
26
+ Exclude:
27
+ - 'spec/**/*_spec.rb'
28
+
29
+ # Disable for generated or gem metadata files
30
+ Gemspec/RequireMFA:
31
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,4 +1,57 @@
1
- ## [Unreleased]
1
+ ## [1.2.0] - 2026-01-10
2
+
3
+ ### Added
4
+ - **v6 API Support**: Migrated from deprecated v4 to v6 ExchangeRate-API
5
+ - Dual-mode operation: authenticated (with API key) and open access (without API key)
6
+ - API key is now optional - gem works perfectly without it
7
+ - Improved error handling for v6-specific errors (invalid-key, quota-reached, inactive-account, unsupported-code)
8
+ - Helpful logging to inform users which API mode they're using
9
+ - **Input Validation**: Comprehensive validation for all conversion inputs
10
+ - Validates amounts (rejects nil, negative, and non-numeric values)
11
+ - Validates currency codes (enforces 3-letter uppercase ISO 4217 format)
12
+ - New error classes: `InvalidAmountError` and `InvalidCurrencyError`
13
+ - Clear, actionable error messages for debugging
14
+ - **HTTP Timeout Configuration**: Configurable timeout to prevent hanging requests
15
+ - Default timeout: 10 seconds
16
+ - Prevents indefinite hangs on slow/dead connections
17
+ - New `TimeoutError` exception for timeout scenarios
18
+ - Configurable via `CurrencyConverter.configure { |c| c.timeout = 15 }`
19
+ - **Test Infrastructure**: Achieved 100% code coverage
20
+ - SimpleCov integration for coverage tracking
21
+ - 43 comprehensive test examples covering all code paths
22
+ - Manual integration test script for real API verification
23
+ - Zero RuboCop offenses
24
+
25
+ ### Fixed
26
+ - **Cache Duration Bug**: Cache expiration now works correctly
27
+ - Previously configured `cache_duration` was ignored
28
+ - Cache now properly expires after the configured duration
29
+ - Fixed by passing `expires_in` parameter to ActiveSupport::Cache
30
+ - **API Key Usage**: API key is now properly used in authenticated requests
31
+ - v1.1.0 configured but never used the API key
32
+ - v6 authenticated mode now includes key in URL path
33
+
34
+ ### Changed
35
+ - **Breaking Change**: Migrated from v4 to v6 API
36
+ - v4 endpoint: `https://api.exchangerate-api.com/v4/latest/{currency}`
37
+ - v6 authenticated: `https://v6.exchangerate-api.com/v6/{API_KEY}/latest/{currency}`
38
+ - v6 open access: `https://open.er-api.com/v6/latest/{currency}`
39
+ - No breaking changes for existing users - works with or without API key
40
+ - Code quality improvements: All RuboCop offenses fixed
41
+
42
+ ### Migration Guide from v1.1.0 to v1.2.0
43
+ - **No code changes required** - fully backward compatible
44
+ - API key is now optional (but recommended for better rate limits)
45
+ - Invalid inputs (nil, negative amounts, invalid currency codes) will now raise validation errors
46
+ - Cache will now properly expire after configured duration (was broken in v1.1.0)
47
+ - Set timeout if needed: `CurrencyConverter.configure { |c| c.timeout = 15 }`
48
+
49
+ ## [1.1.0] - 2024-11-13
50
+ ### Added
51
+ - Added caching support for better performance.
52
+ - Introduced error handling for missing exchange rates.
53
+ ### Fixed
54
+ - Fixed issue with conversion when rate is missing.
2
55
 
3
56
  ## [0.1.0] - 2024-11-11
4
57
 
data/Gemfile CHANGED
@@ -11,4 +11,11 @@ gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
13
 
14
- gem 'stringio', '~> 3.1.2'
14
+ gem "stringio", "~> 3.1.2"
15
+
16
+ gem "activesupport", "~> 6.1"
17
+
18
+ group :test do
19
+ gem "simplecov", require: false
20
+ gem "webmock"
21
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,98 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ currency-converter (1.2.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (6.1.7.10)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (>= 1.6, < 2)
12
+ minitest (>= 5.1)
13
+ tzinfo (~> 2.0)
14
+ zeitwerk (~> 2.3)
15
+ addressable (2.8.7)
16
+ public_suffix (>= 2.0.2, < 7.0)
17
+ ast (2.4.2)
18
+ bigdecimal (3.1.8)
19
+ concurrent-ruby (1.3.4)
20
+ crack (1.0.0)
21
+ bigdecimal
22
+ rexml
23
+ diff-lcs (1.5.1)
24
+ docile (1.4.1)
25
+ hashdiff (1.1.2)
26
+ i18n (1.14.6)
27
+ concurrent-ruby (~> 1.0)
28
+ json (2.8.1)
29
+ language_server-protocol (3.17.0.3)
30
+ minitest (5.25.1)
31
+ parallel (1.26.3)
32
+ parser (3.3.6.0)
33
+ ast (~> 2.4.1)
34
+ racc
35
+ public_suffix (6.0.1)
36
+ racc (1.8.1)
37
+ rainbow (3.1.1)
38
+ rake (13.2.1)
39
+ regexp_parser (2.9.2)
40
+ rexml (3.3.9)
41
+ rspec (3.13.0)
42
+ rspec-core (~> 3.13.0)
43
+ rspec-expectations (~> 3.13.0)
44
+ rspec-mocks (~> 3.13.0)
45
+ rspec-core (3.13.2)
46
+ rspec-support (~> 3.13.0)
47
+ rspec-expectations (3.13.3)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.13.0)
50
+ rspec-mocks (3.13.2)
51
+ diff-lcs (>= 1.2.0, < 2.0)
52
+ rspec-support (~> 3.13.0)
53
+ rspec-support (3.13.1)
54
+ rubocop (1.68.0)
55
+ json (~> 2.3)
56
+ language_server-protocol (>= 3.17.0)
57
+ parallel (~> 1.10)
58
+ parser (>= 3.3.0.2)
59
+ rainbow (>= 2.2.2, < 4.0)
60
+ regexp_parser (>= 2.4, < 3.0)
61
+ rubocop-ast (>= 1.32.2, < 2.0)
62
+ ruby-progressbar (~> 1.7)
63
+ unicode-display_width (>= 2.4.0, < 3.0)
64
+ rubocop-ast (1.35.0)
65
+ parser (>= 3.3.1.0)
66
+ ruby-progressbar (1.13.0)
67
+ simplecov (0.22.0)
68
+ docile (~> 1.1)
69
+ simplecov-html (~> 0.11)
70
+ simplecov_json_formatter (~> 0.1)
71
+ simplecov-html (0.13.2)
72
+ simplecov_json_formatter (0.1.4)
73
+ stringio (3.1.2)
74
+ tzinfo (2.0.6)
75
+ concurrent-ruby (~> 1.0)
76
+ unicode-display_width (2.6.0)
77
+ webmock (3.24.0)
78
+ addressable (>= 2.8.0)
79
+ crack (>= 0.3.2)
80
+ hashdiff (>= 0.4.0, < 2.0.0)
81
+ zeitwerk (2.6.18)
82
+
83
+ PLATFORMS
84
+ x86_64-darwin-23
85
+ x86_64-linux
86
+
87
+ DEPENDENCIES
88
+ activesupport (~> 6.1)
89
+ currency-converter!
90
+ rake (~> 13.0)
91
+ rspec (~> 3.0)
92
+ rubocop (~> 1.21)
93
+ simplecov
94
+ stringio (~> 3.1.2)
95
+ webmock
96
+
97
+ BUNDLED WITH
98
+ 2.2.33
data/README.md CHANGED
@@ -1,13 +1,17 @@
1
1
  # CurrencyConverter
2
2
 
3
- CurrencyConverter is a Ruby gem for converting currencies based on real-time exchange rates.
3
+ The CurrencyConverter gem provides an easy way to perform currency conversions. It allows you to convert amounts between different currencies using real-time exchange rates from ExchangeRate-API, while offering caching for performance improvements and comprehensive error handling for robustness.
4
+
5
+ **Version 1.2.0** brings significant improvements including v6 API support, input validation, HTTP timeout protection, and 100% test coverage.
6
+
7
+ Author: Developed by [Shobhit Jain](https://github.com/shobhits7).
4
8
 
5
9
  ## Installation
6
10
 
7
11
  Add this line to your application's Gemfile:
8
12
 
9
13
  ```ruby
10
- gem 'currency_converter'
14
+ gem 'currency-converter', '~> 1.2.0'
11
15
  ```
12
16
 
13
17
  And then execute:
@@ -16,18 +20,215 @@ And then execute:
16
20
 
17
21
  Or install it yourself as:
18
22
 
19
- $ gem install currency_converter
23
+ $ gem install currency-converter
24
+
25
+ ## Configuration
26
+
27
+ The gem can be used with or without configuration. For better rate limits and performance, it's recommended to configure it with an API key.
28
+
29
+ ### Basic Usage (No Configuration Required)
30
+
31
+ The gem works out of the box using the v6 open access API:
32
+
33
+ ```ruby
34
+ converter = CurrencyConverter::Converter.new
35
+ amount_in_eur = converter.convert(100, 'USD', 'EUR')
36
+ ```
37
+
38
+ ### Advanced Configuration (Recommended)
39
+
40
+ To get your free API key, sign up at [Exchange Rate API](https://www.exchangerate-api.com/)
41
+
42
+ ```ruby
43
+ CurrencyConverter.configure do |config|
44
+ config.api_key = 'your_api_key_here' # Optional: For better rate limits (v6 authenticated API)
45
+ config.cache_duration = 3600 # Optional: Cache duration in seconds (default: 1 hour)
46
+ config.timeout = 10 # Optional: HTTP timeout in seconds (default: 10)
47
+ config.logger = Logger.new(STDOUT) # Optional: Configure logging
48
+ end
49
+ ```
50
+
51
+ **Configuration Options:**
52
+
53
+ - **api_key** (Optional): Your API key for v6 authenticated access with higher rate limits. Without it, the gem uses the v6 open access API (free tier).
54
+ - **cache_duration** (Optional): Duration in seconds for caching exchange rates. Default: 3600 seconds (1 hour).
55
+ - **timeout** (Optional): HTTP request timeout in seconds. Prevents hanging on slow connections. Default: 10 seconds.
56
+ - **logger** (Optional): Logger instance for debugging and monitoring. Default: Logger writing to STDOUT.
57
+
20
58
 
21
59
  ## Usage
22
60
 
61
+ ### Creating an Instance of the Converter
62
+
63
+ Create an instance of the `CurrencyConverter::Converter` class to perform currency conversions:
64
+
65
+ ```ruby
66
+ converter = CurrencyConverter::Converter.new
67
+ ```
68
+
69
+ ### Converting Currency
70
+
71
+ Use the `convert` method to convert an amount from one currency to another:
72
+
73
+ ```ruby
74
+ # Convert 100 USD to EUR
75
+ amount_in_eur = converter.convert(100, 'USD', 'EUR')
76
+ puts amount_in_eur # => 85.0 (example rate)
77
+
78
+ # Convert with decimal amounts
79
+ amount_in_gbp = converter.convert(99.99, 'USD', 'GBP')
80
+ puts amount_in_gbp # => 75.99 (example rate)
81
+ ```
82
+
83
+ **Parameters:**
84
+
85
+ - **amount** (Numeric): The amount to convert. Must be a positive number (Integer or Float).
86
+ - **from_currency** (String): Source currency code in ISO 4217 format (3 uppercase letters, e.g., "USD").
87
+ - **to_currency** (String): Target currency code in ISO 4217 format (3 uppercase letters, e.g., "EUR").
88
+
89
+ **Returns:** The converted amount as a Float, rounded to 2 decimal places.
90
+
91
+ ### Input Validation
92
+
93
+ Version 1.2.0 includes comprehensive input validation:
94
+
95
+ ```ruby
96
+ # Valid inputs
97
+ converter.convert(100, 'USD', 'EUR') # ✓ Valid
98
+ converter.convert(0, 'USD', 'EUR') # ✓ Valid (zero is allowed)
99
+ converter.convert(99.99, 'USD', 'GBP') # ✓ Valid (decimals allowed)
100
+
101
+ # Invalid inputs that will raise errors
102
+ converter.convert(nil, 'USD', 'EUR') # ✗ Raises InvalidAmountError
103
+ converter.convert(-100, 'USD', 'EUR') # ✗ Raises InvalidAmountError
104
+ converter.convert('100', 'USD', 'EUR') # ✗ Raises InvalidAmountError
105
+ converter.convert(100, 'usd', 'EUR') # ✗ Raises InvalidCurrencyError (must be uppercase)
106
+ converter.convert(100, 'US', 'EUR') # ✗ Raises InvalidCurrencyError (must be 3 letters)
107
+ converter.convert(100, nil, 'EUR') # ✗ Raises InvalidCurrencyError
108
+ ```
109
+
110
+ ## Error Handling
111
+
112
+ Version 1.2.0 provides comprehensive error handling with specific exception types:
113
+
114
+ ### Exception Types
115
+
116
+ - **`InvalidAmountError`**: Raised when the amount is invalid (nil, negative, or non-numeric)
117
+ - **`InvalidCurrencyError`**: Raised when currency codes are invalid (nil, wrong format, not ISO 4217)
118
+ - **`TimeoutError`**: Raised when the API request times out
119
+ - **`APIError`**: Raised for API-related failures (network errors, missing rates, API quota reached)
120
+ - **`RateNotFoundError`**: Raised when a specific currency rate is not available from the API
121
+
122
+ ### Error Handling Examples
123
+
23
124
  ```ruby
24
- require "currency_converter"
125
+ begin
126
+ amount_in_eur = converter.convert(100, 'USD', 'EUR')
127
+ puts "Converted amount: #{amount_in_eur} EUR"
128
+ rescue CurrencyConverter::InvalidAmountError => e
129
+ puts "Invalid amount: #{e.message}"
130
+ rescue CurrencyConverter::InvalidCurrencyError => e
131
+ puts "Invalid currency: #{e.message}"
132
+ rescue CurrencyConverter::TimeoutError => e
133
+ puts "Request timed out: #{e.message}"
134
+ rescue CurrencyConverter::APIError => e
135
+ puts "API error: #{e.message}"
136
+ rescue StandardError => e
137
+ puts "Unexpected error: #{e.message}"
138
+ end
139
+ ```
140
+
141
+ ### Common Error Messages
25
142
 
26
- converter = CurrencyConverter::Converter.new("your_api_key")
27
- amount_in_eur = converter.convert(100, "USD", "EUR")
28
- puts "Converted amount: #{amount_in_eur} EUR"
143
+ ```ruby
144
+ # Invalid amount errors
145
+ "Amount cannot be nil"
146
+ "Amount must be a number"
147
+ "Amount cannot be negative"
148
+
149
+ # Invalid currency errors
150
+ "from_currency cannot be nil"
151
+ "from_currency must be a 3-letter uppercase code (e.g., 'USD')"
152
+ "to_currency must be a 3-letter uppercase code (e.g., 'EUR')"
153
+
154
+ # API errors
155
+ "Request timed out after 10 seconds"
156
+ "Invalid API key provided"
157
+ "API rate limit quota reached"
158
+ "Unsupported currency code: XYZ"
159
+ "EUR rate not available"
29
160
  ```
30
161
 
162
+ ## Testing
163
+
164
+ The gem has **100% test coverage** with comprehensive test suites covering all functionality.
165
+
166
+ ### Running Tests
167
+
168
+ ```bash
169
+ # Run all tests
170
+ bundle exec rspec
171
+
172
+ # Run with coverage report
173
+ bundle exec rspec
174
+ # View coverage report: open coverage/index.html
175
+
176
+ # Run code quality checks
177
+ bundle exec rubocop
178
+
179
+ # Run manual integration tests (with real API)
180
+ ruby manual_integration_test.rb
181
+ ```
182
+
183
+ ### Test Coverage
184
+
185
+ **Version 1.2.0 Test Statistics:**
186
+ - **43 test examples**, 0 failures
187
+ - **100% code coverage** (125/125 lines)
188
+ - **0 RuboCop offenses**
189
+ - All tests use WebMock to stub external API calls for consistency
190
+ - Manual integration test script included for real API verification
191
+
192
+ ### Example Test
193
+
194
+ ```ruby
195
+ RSpec.describe CurrencyConverter::Converter do
196
+ let(:converter) { CurrencyConverter::Converter.new }
197
+
198
+ it 'performs currency conversion successfully' do
199
+ # WebMock stubs the API call
200
+ allow_any_instance_of(CurrencyConverter::APIClient)
201
+ .to receive(:get_rate)
202
+ .and_return(0.85)
203
+
204
+ result = converter.convert(100, 'USD', 'EUR')
205
+ expect(result).to eq(85.0)
206
+ end
207
+
208
+ it 'raises InvalidAmountError for negative amounts' do
209
+ expect { converter.convert(-100, 'USD', 'EUR') }
210
+ .to raise_error(CurrencyConverter::InvalidAmountError)
211
+ end
212
+
213
+ it 'raises InvalidCurrencyError for invalid currency codes' do
214
+ expect { converter.convert(100, 'usd', 'EUR') }
215
+ .to raise_error(CurrencyConverter::InvalidCurrencyError)
216
+ end
217
+ end
218
+ ```
219
+
220
+ ## Changelog
221
+
222
+ See [CHANGELOG.md](CHANGELOG.md) for detailed version history and release notes.
223
+
224
+ **Latest Release (v1.2.0 - 2026-01-10):**
225
+ - Migrated to ExchangeRate-API v6 with dual-mode support
226
+ - Added comprehensive input validation
227
+ - Fixed cache duration implementation
228
+ - Added HTTP timeout configuration
229
+ - Achieved 100% test coverage
230
+ - Zero code quality issues
231
+
31
232
  ## Development
32
233
 
33
234
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -36,6 +237,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
36
237
 
37
238
  ## Contributing
38
239
 
240
+ If you'd like to contribute to the development of this gem, feel free to fork the repository and create a pull request with your changes. Ensure that you write tests for any new features or bug fixes, and follow the style guide for Ruby code and RSpec testing.
241
+
39
242
  Bug reports and pull requests are welcome on GitHub at https://github.com/shobhits7/currency_converter. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/shobhits7/currency_converter/blob/main/CODE_OF_CONDUCT.md).
40
243
 
41
244
  ## License
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/currency_converter/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "currency-converter"
7
+ spec.version = CurrencyConverter::VERSION
8
+ spec.authors = ["Shobhit Jain"]
9
+ spec.email = ["shobjain09@gmail.com"]
10
+
11
+ spec.summary = "A simple gem for currency conversion."
12
+ spec.description = "Fetches real-time currency exchange rates and allows currency conversion."
13
+ spec.homepage = "https://github.com/shobhits7/currency_converter"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/shobhits7/currency_converter"
19
+ # spec.metadata["changelog_uri"] = "https://github.com/shobhits7/currency_converter"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = `git ls-files -z`.split("\x0").grep_v(/\.gem$/)
24
+
25
+ # spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ # `git ls-files -z`.split("\x0").reject do |f|
27
+ # (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ # end
29
+ # end
30
+
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+ end