statbank_denmark 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ff97514ce9d09ec1296d7ce144c5a9d104f427c7f5a0ef888d0728779bcc3195
4
+ data.tar.gz: f670282617270e9c6f7d7d826c80728852266e7d13920d4ac585d29af7c6d71c
5
+ SHA512:
6
+ metadata.gz: 383718b874f4b6216078e3588788bab94a9723bdcf37fe35116a05e9cfefd849dadc5513a07a9a8fc921797db0e76c2967273793a517f4a08f7b1c5031552da3
7
+ data.tar.gz: e9b17ec3220fa02870fcb5d80d086507f901631042062cb89a3be5a02ae6fe10360db57ab96e4dc350eed96391933c5e748abe275b3f5dd5691b7ef5238f2336
data/CHANGELOG.txt ADDED
@@ -0,0 +1,9 @@
1
+ # CHANGELOG.md
2
+
3
+ ## [0.0.0] - 2025-09-08
4
+ - Initial release
5
+ - subjects(): /subjects endpoint
6
+ - tables(): /tables endpoint
7
+ - tableinfo(): /tableinfo endpoint
8
+ - data(): /data endpoint
9
+ - search(): Search all tables by string.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # statbank_denmark
2
+
3
+ A Ruby client for easy access to StatBank; Denmark's official statistics (Danmarks Statistik) REST API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'statbank_denmark'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install statbank_denmark
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Defaults for all interfaces
28
+ ```ruby
29
+ lang: 'en' # also can be 'da'
30
+ format: 'json' # except for data(), where it defaults to format: 'csv' and JSON data requires format: 'jsonstat'
31
+ ```
32
+
33
+ ### Via module methods
34
+ ```ruby
35
+ # Get all subjects in English
36
+ StatBankDenmark.subjects # implicitly lang: 'en'
37
+ StatBankDenmark.subjects(lang: 'en') # explicitly lang: 'en'
38
+
39
+ # Get all subjects in Danish
40
+ StatBankDenmark.subjects(lang: 'da')
41
+
42
+ # Get subjects recursively
43
+ StatBankDenmark.subjects(recursive: true)
44
+
45
+ # Get a list of all tables
46
+ StatBankDenmark.tables
47
+
48
+ # Get a list of tables about a specific subject by code
49
+ StatBankDenmark.tables(subject: 4)
50
+
51
+ # Get a list of tables which are inactive
52
+ StatBankDenmark.tables(include_inactive: true)
53
+
54
+ # Get a list of tables which are inactive on a specific subject by code
55
+ StatBankDenmark.tables(include_inactive: true, subject: 4)
56
+
57
+ # Get info on a specific table
58
+ StatBankDenmark.table_info('STRAF42') # implicitly format: 'json'
59
+ StatBankDenmark.table_info('STRAF42', format: 'json') # explicitly format: 'json'
60
+ StatBankDenmark.table_info('STRAF42', format: 'csv') # format: 'csv'
61
+
62
+ # Get data from a specific table
63
+ StatBankDenmark.data('STRAF42') # implicitly format: 'csv'
64
+ StatBankDenmark.data('STRAF42', format: 'csv') # explicitly format: 'csv'
65
+ StatBankDenmark.data('STRAF42', format: 'jsonstat') # Returns json and must be specified with /jsonstat/i, not /json/i.
66
+
67
+ # Search tables for metadata strings
68
+ StatBankDenmark.search('population')
69
+ StatBankDenmark.search('POPULATION') # case insensitive search
70
+ StatBankDenmark.search('befolkning', lang: 'da') # lang: 'da'
71
+ StatBankDenmark.search('BEFOLKNING', lang: 'da') # case insensitive search and lang: 'da'
72
+ ```
73
+
74
+ ### Via instantiation
75
+ ```ruby
76
+ client = StatBankDenmark.new
77
+
78
+ # Get all subjects in English
79
+ client.subjects # implicitly lang: 'en'
80
+ client.subjects(lang: 'en') # explicitly lang: 'en'
81
+
82
+ # Get all subjects in Danish
83
+ client.subjects(lang: 'da')
84
+
85
+ # Get subjects recursively
86
+ client.subjects(recursive: true)
87
+
88
+ # Get a list of all tables
89
+ client.tables
90
+
91
+ # Get a list of tables about a specific subject by code
92
+ client.tables(subject: 4)
93
+
94
+ # Get a list of tables which are inactive
95
+ client.tables(include_inactive: true)
96
+
97
+ # Get a list of tables which are inactive on a specific subject by code
98
+ client.tables(include_inactive: true, subject: 4)
99
+
100
+ # Get info on a specific table
101
+ client.table_info('STRAF42') # implicitly format: 'json'
102
+ client.table_info('STRAF42', format: 'json') # explicitly format: 'json'
103
+ client.table_info('STRAF42', format: 'csv') # format: 'csv'
104
+
105
+ # Get data from a specific table
106
+ client.data('STRAF42') # implicitly format: 'csv'
107
+ client.data('STRAF42', format: 'csv') # explicitly format: 'csv'
108
+ client.data('STRAF42', format: 'jsonstat') # Returns json and must be specified with /jsonstat/i, not /json/i.
109
+
110
+ # Search tables for metadata strings
111
+ client.search('population')
112
+ client.search('POPULATION') # case insensitive search
113
+ client.search('befolkning', lang: 'da') # lang: 'da'
114
+ client.search('BEFOLKNING', lang: 'da') # case insensitive search and lang: 'da'
115
+ ```
116
+
117
+ ## Contributing
118
+
119
+ 1. Fork it (https://github.com/thoran/statbank_denmark/fork)
120
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
121
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
122
+ 4. Push to the branch (`git push origin my-new-feature`)
123
+ 5. Create a new pull request
124
+
125
+ ## License
126
+
127
+ The gem is available as open source under the terms of the [Ruby License](https://opensource.org/licenses/MIT).
data/lib/Object/inQ.rb ADDED
@@ -0,0 +1,15 @@
1
+ # Object/inQ.rb
2
+ # Object#in?
3
+
4
+ # 20250906
5
+ # 0.5.1
6
+
7
+ # Description: This is essentially the reverse of Enumerable#include?. It can be pointed at any object and takes a collection as an argument.
8
+
9
+ class Object
10
+
11
+ def in?(*a)
12
+ a.flatten.include?(self)
13
+ end
14
+
15
+ end
@@ -0,0 +1,199 @@
1
+ # lib/StatBankDenmark/Client.rb
2
+ # StatBankDenmark::Client
3
+
4
+ gem 'http.rb'; require 'http.rb'
5
+ require 'json'
6
+
7
+ require_relative './Error'
8
+ require_relative '../Object/inQ'
9
+
10
+ module StatBankDenmark
11
+ class Client
12
+ ALLOWABLE_VERBS = %w{GET POST}
13
+ API_HOST = 'api.statbank.dk'
14
+
15
+ class << self
16
+ def path_prefix
17
+ '/v1'
18
+ end
19
+ end # class << self
20
+
21
+ def subjects(
22
+ format: 'json',
23
+ include_tables: nil,
24
+ lang: 'en',
25
+ omit_empty: nil,
26
+ recursive: nil
27
+ )
28
+ response = get(
29
+ path: '/subjects',
30
+ args: {
31
+ format: format,
32
+ includeTables: include_tables,
33
+ lang: lang,
34
+ omitEmptySubjects: omit_empty,
35
+ recursive: recursive,
36
+ }
37
+ )
38
+ handle_response(response)
39
+ end
40
+
41
+ def tables(
42
+ format: 'json',
43
+ include_inactive: nil,
44
+ lang: 'en',
45
+ subject: nil
46
+ )
47
+ response = get(
48
+ path: '/tables',
49
+ args: {
50
+ format: format,
51
+ includeInactive: include_inactive,
52
+ lang: lang,
53
+ subjects: subject,
54
+ }
55
+ )
56
+ handle_response(response)
57
+ end
58
+
59
+ def table_info(
60
+ table_id,
61
+ format: 'json',
62
+ lang: 'en'
63
+ )
64
+ raise ArgumentError, "table_id is required" if table_id.nil? || table_id.empty?
65
+ response = get(
66
+ path: '/tableinfo',
67
+ args: {
68
+ format: format,
69
+ lang: lang,
70
+ id: table_id,
71
+ }
72
+ )
73
+ handle_response(response)
74
+ end
75
+
76
+ def data(
77
+ table_id,
78
+ format: 'csv',
79
+ lang: 'en',
80
+ value_presentation: 'Default',
81
+ variables: {}
82
+ )
83
+ raise ArgumentError, "table_id is required" if table_id.nil? || table_id.empty?
84
+ body = {
85
+ table: table_id,
86
+ lang: lang,
87
+ format: format,
88
+ valuePresentation: value_presentation,
89
+ }
90
+ unless variables.empty?
91
+ body[:variables] = variables.map do |var_name, values|
92
+ {
93
+ code: var_name.to_s,
94
+ values: Array(values)
95
+ }
96
+ end
97
+ end
98
+ response = post(path: '/data', args: body)
99
+ handle_response(response)
100
+ end
101
+
102
+ def search(query, lang: 'en')
103
+ all_tables = tables(lang: lang)
104
+ return [] unless all_tables.is_a?(Array)
105
+ all_tables.select do |table|
106
+ text = table['text'] || table[:text] || ''
107
+ text.downcase.include?(query.downcase)
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def initialize(timeout: 30)
114
+ @timeout = timeout
115
+ end
116
+
117
+ def get(path:, args: {})
118
+ do_request(verb: 'GET', path: path, args: args, options: {open_timeout: @timeout, read_timeout: @timeout})
119
+ end
120
+
121
+ def post(path:, args: {})
122
+ do_request(verb: 'POST', path: path, args: args, options: {open_timeout: @timeout, read_timeout: @timeout})
123
+ end
124
+
125
+ def do_request(verb:, path:, args: {}, options: {})
126
+ verb = verb.to_s.upcase
127
+ raise ArgumentError, "Unsupported HTTP method: #{verb}" unless verb.in?(ALLOWABLE_VERBS)
128
+ request_string = request_string(path: path)
129
+ args = args.reject{|k,v| v.nil?}
130
+ headers = {'Content-Type' => 'application/json'}
131
+ HTTP.send(verb.downcase, request_string, args, headers, options)
132
+ end
133
+
134
+ def request_string(path:)
135
+ "https://#{API_HOST}#{self.class.path_prefix}#{path}"
136
+ end
137
+
138
+ def handle_response(response)
139
+ if response.success?
140
+ body = response.body.to_s
141
+ parsed_response(response)
142
+ else
143
+ case response.code.to_i
144
+ when 400
145
+ raise BadRequestError.new(
146
+ code: response.code,
147
+ message: response.message,
148
+ body: response.body
149
+ )
150
+ when 404
151
+ raise NotFoundError.new(
152
+ code: response.code,
153
+ message: response.message,
154
+ body: response.body
155
+ )
156
+ when 429
157
+ raise RateLimitError.new(
158
+ code: response.code,
159
+ message: response.message,
160
+ body: response.body
161
+ )
162
+ when 500..599
163
+ raise ServerError.new(
164
+ code: response.code,
165
+ message: response.message,
166
+ body: response.body
167
+ )
168
+ else
169
+ raise APIError.new(
170
+ code: response.code,
171
+ message: response.message,
172
+ body: response.body
173
+ )
174
+ end
175
+ end
176
+ end
177
+
178
+ def parsed_response(response)
179
+ case response['content-type']
180
+ when /application\/json/
181
+ begin
182
+ JSON.parse(response.body)
183
+ rescue JSON::ParserError
184
+ response.body
185
+ end
186
+ when /text\/csv/
187
+ response.body
188
+ when /application\/xml/, /text\/xml/
189
+ response.body
190
+ else
191
+ begin
192
+ JSON.parse(response.body)
193
+ rescue JSON::ParserError
194
+ response.body
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,35 @@
1
+ # lib/StatBankDenmark/Error.rb
2
+ # StatBankDenmark::Error
3
+
4
+ module StatBankDenmark
5
+ class Error < RuntimeError
6
+ attr_reader\
7
+ :code,
8
+ :message,
9
+ :body
10
+
11
+ def to_s
12
+ "#{self.class} (#{@code}): #{@message}"
13
+ end
14
+
15
+ private
16
+
17
+ def initialize(code:, message:, body:)
18
+ @code = code
19
+ @message = message
20
+ @body = body
21
+ super(message)
22
+ end
23
+ end
24
+
25
+ # 400
26
+ class BadRequestError < Error; end
27
+ # 404
28
+ class NotFoundError < Error; end
29
+ # 429
30
+ class RateLimitError < Error; end
31
+ # 500..599
32
+ class ServerError < Error; end
33
+ # Everything else.
34
+ class APIError < Error; end
35
+ end
@@ -0,0 +1,6 @@
1
+ # lib/StatBankDenmark/VERSION.rb
2
+ # StatBankDenmark::VERSION
3
+
4
+ module StatBankDenmark
5
+ VERSION = '0.0.0'
6
+ end
@@ -0,0 +1,35 @@
1
+ # lib/StatBankDenmark.rb
2
+ # StatBankDenmark
3
+
4
+ require_relative './StatBankDenmark/VERSION'
5
+ require_relative './StatBankDenmark/Client'
6
+
7
+ module StatBankDenmark
8
+ class << self
9
+ def subjects(**options)
10
+ client.subjects(**options)
11
+ end
12
+
13
+ def tables(**options)
14
+ client.tables(**options)
15
+ end
16
+
17
+ def table_info(table_id, **options)
18
+ client.table_info(table_id, **options)
19
+ end
20
+
21
+ def data(table_id, **options)
22
+ client.data(table_id, **options)
23
+ end
24
+
25
+ def search(query, **options)
26
+ client.search(query, **options)
27
+ end
28
+
29
+ private
30
+
31
+ def client
32
+ @client ||= Client.new
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ # lib/statbank_denmark.rb
2
+
3
+ require_relative './StatBankDenmark'
@@ -0,0 +1,36 @@
1
+ require_relative './lib/StatBankDenmark/VERSION'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'statbank_denmark'
5
+
6
+ spec.version = StatBankDenmark::VERSION
7
+ spec.date = '2025-09-09'
8
+
9
+ spec.summary = "A Ruby client for the StatBank Denmark API."
10
+ spec.description = "A Ruby client for easy access to StatBank; Denmark's official statistics (Danmarks Statistik) REST API."
11
+
12
+ spec.authors = 'thoran'
13
+ spec.email = 'code@thoran.com'
14
+ spec.homepage = 'https://github.com/thoran/statbank_denmark'
15
+ spec.license = 'Ruby'
16
+
17
+ spec.required_ruby_version = '>= 2.7'
18
+
19
+ spec.files = [
20
+ 'CHANGELOG.txt',
21
+ 'Gemfile',
22
+ 'README.md',
23
+ 'statbank_denmark.gemspec',
24
+ Dir['lib/**/*.rb'],
25
+ Dir['test/**/*.rb']
26
+ ].flatten
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency('http.rb')
30
+ spec.add_dependency('json')
31
+
32
+ spec.add_development_dependency('minitest')
33
+ spec.add_development_dependency('minitest-spec-context')
34
+ spec.add_development_dependency('webmock')
35
+ spec.add_development_dependency('vcr')
36
+ end
@@ -0,0 +1,316 @@
1
+ # test/StatBankDenmark/Client_test.rb
2
+
3
+ require_relative '../helper'
4
+
5
+ describe StatBankDenmark::Client do
6
+ let(:client){StatBankDenmark::Client.new}
7
+
8
+ describe "#subjects" do
9
+ context "with English language" do
10
+ let(:result){client.subjects(lang: 'en')}
11
+
12
+ it "returns available subjects in English" do
13
+ VCR.use_cassette("subjects_en") do
14
+ _(result).must_be_kind_of(Array)
15
+ _(result).wont_be_empty
16
+ _(result.first.keys).must_include('id')
17
+ _(result.first.keys).must_include('description')
18
+ _(result.first.keys).must_include('active')
19
+ _(result.first.keys).must_include('hasSubjects')
20
+ _(result.first.keys).must_include('subjects')
21
+ end
22
+ end
23
+ end
24
+
25
+ context "with Danish language" do
26
+ let(:result){client.subjects(lang: 'da')}
27
+
28
+ it "returns available subjects in Danish" do
29
+ VCR.use_cassette("subjects_da") do
30
+ _(result).must_be_kind_of(Array)
31
+ _(result).wont_be_empty
32
+ _(result.first.keys).must_include('id')
33
+ _(result.first.keys).must_include('description')
34
+ _(result.first.keys).must_include('active')
35
+ _(result.first.keys).must_include('hasSubjects')
36
+ _(result.first.keys).must_include('subjects')
37
+ end
38
+ end
39
+ end
40
+
41
+ context "with recursive hierarchy enabled" do
42
+ let(:result){client.subjects(recursive: true)}
43
+
44
+ it "handles recursive parameter" do
45
+ VCR.use_cassette("subjects_recursive") do
46
+ _(result).must_be_kind_of(Array)
47
+ end
48
+ end
49
+ end
50
+
51
+ context "when API returns error" do
52
+ it "raises BadRequestError for invalid parameters" do
53
+ WebMock.stub_request(:get, /api\.statbank\.dk/).to_return(status: 400, body: 'Bad Request')
54
+ _(proc{client.subjects(lang: 'invalid')}).must_raise(StatBankDenmark::BadRequestError)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "#tables" do
60
+ context "when retrieving all tables" do
61
+ let(:result){client.tables}
62
+
63
+ it "returns all available tables" do
64
+ VCR.use_cassette("tables_all") do
65
+ _(result).must_be_kind_of(Array)
66
+ _(result).wont_be_empty
67
+ _(result.first).must_include('id')
68
+ _(result.first).must_include('text')
69
+ _(result.first).must_include('variables')
70
+ end
71
+ end
72
+ end
73
+
74
+ context "when filtering by specific subject" do
75
+ let(:result){client.tables(subject: 4)}
76
+
77
+ it "returns tables for crime and justice subject" do
78
+ VCR.use_cassette("tables_subject_crime") do
79
+ _(result).must_be_kind_of(Array)
80
+ _(result).wont_be_empty
81
+ crime_table = result.find{|t| t['id'] == 'STRAF42'}
82
+ _(crime_table).wont_be_nil if crime_table
83
+ end
84
+ end
85
+ end
86
+
87
+ context "when including inactive tables" do
88
+ let(:result){client.tables(include_inactive: true)}
89
+
90
+ it "handles include_inactive parameter" do
91
+ VCR.use_cassette("tables_include_inactive") do
92
+ _(result).must_be_kind_of(Array)
93
+ end
94
+ end
95
+ end
96
+
97
+ it "raises NotFoundError for non-existent subject" do
98
+ WebMock.stub_request(:get, /api\.statbank\.dk/).to_return(status: 404, body: 'Not Found')
99
+ _(proc {client.tables(subject: '999')}).must_raise(StatBankDenmark::NotFoundError)
100
+ end
101
+ end
102
+
103
+ describe "#table_info" do
104
+ context "when requesting metadata in English" do
105
+ let(:result){client.table_info('STRAF42')}
106
+
107
+ it "returns metadata for valid table" do
108
+ VCR.use_cassette("table_info_straf42") do
109
+ _(result).must_be_kind_of(Hash)
110
+ _(result).must_include('id')
111
+ _(result).must_include('text')
112
+ _(result).must_include('variables')
113
+ _(result['id']).must_equal('STRAF42')
114
+
115
+ variables = result['variables']
116
+ _(variables).must_be_kind_of(Array)
117
+ _(variables).wont_be_empty
118
+ _(variables.first).must_include('id')
119
+ _(variables.first).must_include('text')
120
+ end
121
+ end
122
+ end
123
+
124
+ context "when requesting metadata in Danish" do
125
+ let(:result){client.table_info('STRAF42', lang: 'da')}
126
+
127
+ it "returns metadata in Danish" do
128
+ VCR.use_cassette("table_info_straf42_da") do
129
+ _(result).must_be_kind_of(Hash)
130
+ _(result['id']).must_equal('STRAF42')
131
+ end
132
+ end
133
+ end
134
+
135
+ it "raises ArgumentError for nil table_id" do
136
+ _(proc{client.table_info(nil)}).must_raise(ArgumentError)
137
+ end
138
+
139
+ it "raises ArgumentError for empty table_id" do
140
+ _(proc{client.table_info('')}).must_raise(ArgumentError)
141
+ end
142
+
143
+ it "raises NotFoundError for non-existent table" do
144
+ WebMock.stub_request(:get, /api\.statbank\.dk/).to_return(status: 404, body: 'Table not found')
145
+ _(proc{client.table_info('NONEXISTENT')}).must_raise(StatBankDenmark::NotFoundError)
146
+ end
147
+ end
148
+
149
+ describe "#data" do
150
+ context "when requesting CSV format" do
151
+ let(:result) do
152
+ client.data(
153
+ 'STRAF42',
154
+ format: 'csv',
155
+ variables: {'HERKOMST1' => ['00'], 'TID' => ['2023']}
156
+ )
157
+ end
158
+
159
+ it "returns CSV data for table with variables" do
160
+ VCR.use_cassette("data_straf42_csv") do
161
+ _(result).must_be_kind_of(String)
162
+ _(result).must_include('HERKOMST1')
163
+ _(result).must_include('2023')
164
+ _(result.lines.length).must_be(:>, 1)
165
+ end
166
+ end
167
+ end
168
+
169
+ context "when requesting JSON format" do
170
+ let(:result) do
171
+ client.data(
172
+ 'STRAF42',
173
+ format: 'jsonstat',
174
+ variables: {'HERKOMST1' => ['00'], 'TID' => ['2023']}
175
+ )
176
+ end
177
+
178
+ it "returns JSON data for table" do
179
+ VCR.use_cassette("data_straf42_json") do
180
+ _(result).must_be_kind_of(Hash)
181
+ _(result).wont_be_empty
182
+ _(result['dataset']['dimension']).must_include('HERKOMST1')
183
+ _(result['dataset']['dimension']).must_include('ContentsCode')
184
+ _(result['dataset']['dimension']).must_include('Tid')
185
+ _(result['dataset']['dimension']).must_include('id')
186
+ _(result['dataset']['dimension']).must_include('size')
187
+ _(result['dataset']['dimension']).must_include('role')
188
+ end
189
+ end
190
+ end
191
+
192
+ context "when no variables specified" do
193
+ let(:result) do
194
+ client.data(
195
+ 'STRAF42',
196
+ format: 'jsonstat'
197
+ )
198
+ end
199
+
200
+ it "returns all available data" do
201
+ VCR.use_cassette("data_straf42_all") do
202
+ _(result).must_be_kind_of(Hash)
203
+ _(result).wont_be_empty
204
+ end
205
+ end
206
+ end
207
+
208
+ it "raises ArgumentError for nil table_id" do
209
+ _(proc{client.data(nil)}).must_raise(ArgumentError)
210
+ end
211
+
212
+ it "raises ArgumentError for empty table_id" do
213
+ _(proc{client.data('')}).must_raise(ArgumentError)
214
+ end
215
+
216
+ it "raises BadRequestError for invalid variables" do
217
+ WebMock.stub_request(:post, /api\.statbank\.dk/).to_return(status: 400, body: 'Invalid variable specification')
218
+ _(proc{client.data('STRAF42', variables: {'INVALID' => ['bad']})}).must_raise(StatBankDenmark::BadRequestError)
219
+ end
220
+
221
+ it "handles rate limiting" do
222
+ WebMock.stub_request(:post, /api\.statbank\.dk/).to_return(status: 429, body: 'Rate limit exceeded')
223
+ _(proc{client.data('STRAF42')}).must_raise(StatBankDenmark::RateLimitError)
224
+ end
225
+
226
+ it "handles server errors" do
227
+ WebMock.stub_request(:post, /api\.statbank\.dk/).to_return(status: 500, body: 'Internal Server Error')
228
+ _(proc{client.data('STRAF42')}).must_raise(StatBankDenmark::ServerError)
229
+ end
230
+ end
231
+
232
+ describe "#search" do
233
+ context "when searching for existing tables" do
234
+ let(:result){client.search('crime')}
235
+
236
+ it "finds tables by search term" do
237
+ VCR.use_cassette("search_crime") do
238
+ _(result).must_be_kind_of Array
239
+ crime_table = result.find{|t| t['id'] == 'STRAF42'}
240
+ _(crime_table).wont_be_nil if crime_table
241
+ end
242
+ end
243
+ end
244
+
245
+ context "when searching for non-existent tables" do
246
+ let(:result){client.search('xyzzyx_nonexistent_term')}
247
+
248
+ it "returns empty array for no matches" do
249
+ VCR.use_cassette("search_nomatch") do
250
+ _(result).must_be_kind_of(Array)
251
+ _(result).must_be_empty
252
+ end
253
+ end
254
+ end
255
+
256
+ context "when testing case sensitivity" do
257
+ let(:result_lower){client.search('population')}
258
+ let(:result_upper){client.search('POPULATION')}
259
+
260
+ it "is case insensitive" do
261
+ VCR.use_cassette("search_case_insensitive") do
262
+ _(result_lower).must_equal(result_upper)
263
+ end
264
+ end
265
+ end
266
+
267
+ context "when searching in Danish" do
268
+ let(:result){client.search('befolkning', lang: 'da')}
269
+
270
+ it "searches in Danish language" do
271
+ VCR.use_cassette("search_danish") do
272
+ _(result).must_be_kind_of(Array)
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ describe "error handling" do
279
+ context "when network times out" do
280
+ before do
281
+ WebMock.stub_request(:get, /api\.statbank\.dk/).to_timeout
282
+ end
283
+
284
+ it "handles network timeouts" do
285
+ _(proc{client.subjects}).must_raise(StandardError)
286
+ end
287
+ end
288
+
289
+ context "when API returns malformed JSON" do
290
+ let(:result){client.subjects}
291
+
292
+ before do
293
+ WebMock.stub_request(:get, /api\.statbank\.dk/).to_return(
294
+ status: 200,
295
+ headers: {'Content-Type' => 'application/json'},
296
+ body: 'invalid json{'
297
+ )
298
+ end
299
+
300
+ it "handles malformed JSON responses gracefully" do
301
+ _(result).must_equal('invalid json{')
302
+ end
303
+ end
304
+ end
305
+
306
+ describe "private methods" do
307
+ it "builds request URLs correctly" do
308
+ url = client.send(:request_string, path: '/subjects')
309
+ _(url).must_equal('https://api.statbank.dk/v1/subjects')
310
+ end
311
+
312
+ it "validates HTTP verbs" do
313
+ _(proc{client.send(:do_request, verb: 'PATCH', path: '/test')}).must_raise(ArgumentError)
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,45 @@
1
+ # test/StatBankDenmark/Error_test.rb
2
+
3
+ require_relative '../helper'
4
+
5
+ describe StatBankDenmark::Error do
6
+ context "" do
7
+ let(:error) do
8
+ StatBankDenmark::BadRequestError.new(
9
+ code: 400,
10
+ message: "Bad Request",
11
+ body: "Invalid parameter"
12
+ )
13
+ end
14
+
15
+ it "stores error details" do
16
+ _(error.code).must_equal(400)
17
+ _(error.message).must_equal("Bad Request")
18
+ _(error.body).must_equal("Invalid parameter")
19
+ end
20
+ end
21
+
22
+ context "" do
23
+ let(:error) do
24
+ StatBankDenmark::NotFoundError.new(
25
+ code: 404,
26
+ message: "Not Found",
27
+ body: "Table does not exist"
28
+ )
29
+ end
30
+
31
+ it "provides meaningful string representation" do
32
+ _(error.to_s).must_equal("StatBankDenmark::NotFoundError (404): Not Found")
33
+ end
34
+ end
35
+
36
+ describe "error hierarchy" do
37
+ it "has correct inheritance" do
38
+ _(StatBankDenmark::BadRequestError).must_be :<, StatBankDenmark::Error
39
+ _(StatBankDenmark::NotFoundError).must_be :<, StatBankDenmark::Error
40
+ _(StatBankDenmark::RateLimitError).must_be :<, StatBankDenmark::Error
41
+ _(StatBankDenmark::ServerError).must_be :<, StatBankDenmark::Error
42
+ _(StatBankDenmark::APIError).must_be :<, StatBankDenmark::Error
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ # test/StatBankDenmark/VERSION_test.rb
2
+
3
+ require_relative '../helper'
4
+
5
+ describe StatBankDenmark::VERSION do
6
+ it "returns the version number" do
7
+ _(StatBankDenmark::VERSION).must_match(/\d.\d.\d/)
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # test/StatBankDenmark_test.rb
2
+
3
+ require_relative './test_helper'
4
+
5
+ describe StatBankDenmark do
6
+ describe "module methods" do
7
+ it "responds to core API methods" do
8
+ _(StatBankDenmark).must_respond_to(:subjects)
9
+ _(StatBankDenmark).must_respond_to(:tables)
10
+ _(StatBankDenmark).must_respond_to(:table_info)
11
+ _(StatBankDenmark).must_respond_to(:data)
12
+ _(StatBankDenmark).must_respond_to(:search)
13
+ end
14
+ end
15
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,15 @@
1
+ # test/helper.rb
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest-spec-context'
5
+ require 'webmock/minitest'
6
+ require 'vcr'
7
+
8
+ require_relative '../lib/statbank_denmark'
9
+
10
+ VCR.configure do |config|
11
+ config.cassette_library_dir = File.join(__dir__, 'fixtures', 'vcr_cassettes')
12
+ config.hook_into :webmock
13
+ config.default_cassette_options = {record: :once}
14
+ config.allow_http_connections_when_no_cassette = true
15
+ end
@@ -0,0 +1,40 @@
1
+ # test/integration_test.rb
2
+ # Note: This test bypasses VCR and makes real API calls.
3
+
4
+ require_relative './helper'
5
+
6
+ describe "Integration Tests", :integration do
7
+ let(:client){StatBankDenmark::Client.new}
8
+
9
+ before do
10
+ skip unless ENV['RUN_INTEGRATION_TESTS']
11
+ end
12
+
13
+ it "can fetch real data from Danish Statistics API" do
14
+ # Test the API
15
+ subjects = client.subjects
16
+ _(subjects).must_be_kind_of(Array)
17
+ _(subjects).wont_be_empty
18
+
19
+ # Find People subject
20
+ people_subject = subjects.find{|s| s['description']&.downcase&.include?('people')}
21
+ skip "People subject not found" unless people_subject
22
+
23
+ # Get People tables
24
+ tables = client.tables(subject: people_subject['id'])
25
+ _(tables).must_be_kind_of(Array)
26
+
27
+ # Find FOLK1A table
28
+ folk1a = tables.find{|t| t['id'] == 'FOLK1A'}
29
+ skip "FOLK1A table not found" unless folk1a
30
+
31
+ # Get table metadata
32
+ info = client.table_info('FOLK1A')
33
+ _(info['id']).must_equal('FOLK1A')
34
+
35
+ # Get some actual data (limited to avoid large responses)
36
+ data = client.data('FOLK1A', format: 'jsonstat')
37
+ _(data).must_be_kind_of(Hash)
38
+ _(data).wont_be_empty
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: statbank_denmark
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - thoran
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-09-09 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: http.rb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest-spec-context
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: webmock
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: vcr
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ description: A Ruby client for easy access to StatBank; Denmark's official statistics
97
+ (Danmarks Statistik) REST API.
98
+ email: code@thoran.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.txt
104
+ - Gemfile
105
+ - README.md
106
+ - lib/Object/inQ.rb
107
+ - lib/StatBankDenmark.rb
108
+ - lib/StatBankDenmark/Client.rb
109
+ - lib/StatBankDenmark/Error.rb
110
+ - lib/StatBankDenmark/VERSION.rb
111
+ - lib/statbank_denmark.rb
112
+ - statbank_denmark.gemspec
113
+ - test/StatBankDenmark/Client_test.rb
114
+ - test/StatBankDenmark/Error_test.rb
115
+ - test/StatBankDenmark/VERSION_test.rb
116
+ - test/StatBankDenmark_test.rb
117
+ - test/helper.rb
118
+ - test/integration_test.rb
119
+ homepage: https://github.com/thoran/statbank_denmark
120
+ licenses:
121
+ - Ruby
122
+ metadata: {}
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '2.7'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.7.1
138
+ specification_version: 4
139
+ summary: A Ruby client for the StatBank Denmark API.
140
+ test_files: []