baza.rb 0.4.0 → 0.5.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 +4 -4
- data/.github/workflows/copyrights.yml +1 -1
- data/.github/workflows/markdown-lint.yml +1 -1
- data/.github/workflows/typos.yml +19 -0
- data/.gitignore +3 -2
- data/.rubocop.yml +1 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +22 -23
- data/README.md +1 -1
- data/REUSE.toml +7 -7
- data/Rakefile +10 -6
- data/lib/baza-rb/fake.rb +49 -38
- data/lib/baza-rb/version.rb +2 -2
- data/lib/baza-rb.rb +125 -76
- data/test/test__helper.rb +20 -6
- data/test/test_baza-rb.rb +272 -21
- data/test/test_fake.rb +1 -1
- metadata +5 -4
data/lib/baza-rb.rb
CHANGED
@@ -16,11 +16,16 @@ require 'typhoeus'
|
|
16
16
|
require 'zlib'
|
17
17
|
require_relative 'baza-rb/version'
|
18
18
|
|
19
|
-
#
|
19
|
+
# Ruby client for the Zerocracy API.
|
20
20
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
21
|
+
# This class provides a complete interface to interact with the Zerocracy
|
22
|
+
# platform API. Create an instance with your authentication token and use
|
23
|
+
# its methods to manage jobs, transfer funds, handle durables, and more.
|
24
|
+
#
|
25
|
+
# @example Basic usage
|
26
|
+
# baza = BazaRb.new('api.zerocracy.com', 443, 'your-token-here')
|
27
|
+
# puts baza.whoami # => "your-github-username"
|
28
|
+
# puts baza.balance # => 100.5
|
24
29
|
#
|
25
30
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
26
31
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
@@ -35,16 +40,16 @@ class BazaRb
|
|
35
40
|
# Unexpected response arrived from the server.
|
36
41
|
class BadResponse < StandardError; end
|
37
42
|
|
38
|
-
#
|
43
|
+
# Initialize a new Zerocracy API client.
|
39
44
|
#
|
40
|
-
# @param [String] host
|
41
|
-
# @param [Integer] port TCP port
|
42
|
-
# @param [String] token
|
43
|
-
# @param [Boolean] ssl
|
44
|
-
# @param [Float] timeout
|
45
|
-
# @param [Integer] retries
|
46
|
-
# @param [Loog] loog The logging facility
|
47
|
-
# @param [Boolean] compress
|
45
|
+
# @param [String] host The API host name (e.g., 'api.zerocracy.com')
|
46
|
+
# @param [Integer] port The TCP port to connect to (usually 443 for HTTPS)
|
47
|
+
# @param [String] token Your Zerocracy API authentication token
|
48
|
+
# @param [Boolean] ssl Whether to use SSL/HTTPS (default: true)
|
49
|
+
# @param [Float] timeout Connection and request timeout in seconds (default: 30)
|
50
|
+
# @param [Integer] retries Number of retries on connection failure (default: 3)
|
51
|
+
# @param [Loog] loog The logging facility (default: Loog::NULL)
|
52
|
+
# @param [Boolean] compress Whether to use GZIP compression for requests/responses (default: true)
|
48
53
|
def initialize(host, port, token, ssl: true, timeout: 30, retries: 3, loog: Loog::NULL, compress: true)
|
49
54
|
@host = host
|
50
55
|
@port = port
|
@@ -58,7 +63,8 @@ class BazaRb
|
|
58
63
|
|
59
64
|
# Get GitHub login name of the logged in user.
|
60
65
|
#
|
61
|
-
# @return [String] GitHub nickname
|
66
|
+
# @return [String] GitHub nickname of the authenticated user
|
67
|
+
# @raise [ServerFailure] If authentication fails or server returns an error
|
62
68
|
def whoami
|
63
69
|
nick = nil
|
64
70
|
elapsed(@loog) do
|
@@ -77,9 +83,10 @@ class BazaRb
|
|
77
83
|
nick
|
78
84
|
end
|
79
85
|
|
80
|
-
# Get current balance
|
86
|
+
# Get current balance of the authenticated user.
|
81
87
|
#
|
82
|
-
# @return [Float] The balance,
|
88
|
+
# @return [Float] The balance in zents (Ƶ), where 1 Ƶ = 1 USDT
|
89
|
+
# @raise [ServerFailure] If authentication fails or server returns an error
|
83
90
|
def balance
|
84
91
|
z = nil
|
85
92
|
elapsed(@loog) do
|
@@ -98,12 +105,13 @@ class BazaRb
|
|
98
105
|
z
|
99
106
|
end
|
100
107
|
|
101
|
-
# Push factbase to the server.
|
108
|
+
# Push factbase to the server to create a new job.
|
102
109
|
#
|
103
|
-
# @param [String] name The name of the job on the server
|
104
|
-
# @param [
|
105
|
-
# @param [Array<String>] meta List of
|
106
|
-
# @return [Integer] Job ID
|
110
|
+
# @param [String] name The unique name of the job on the server
|
111
|
+
# @param [String] data The binary data to push to the server (factbase content)
|
112
|
+
# @param [Array<String>] meta List of metadata strings to attach to the job
|
113
|
+
# @return [Integer] Job ID assigned by the server
|
114
|
+
# @raise [ServerFailure] If the push operation fails
|
107
115
|
def push(name, data, meta)
|
108
116
|
raise 'The "name" of the job is nil' if name.nil?
|
109
117
|
raise 'The "name" of the job may not be empty' if name.empty?
|
@@ -139,10 +147,11 @@ class BazaRb
|
|
139
147
|
id
|
140
148
|
end
|
141
149
|
|
142
|
-
# Pull factbase from the server.
|
150
|
+
# Pull factbase from the server for a specific job.
|
143
151
|
#
|
144
152
|
# @param [Integer] id The ID of the job on the server
|
145
|
-
# @return [
|
153
|
+
# @return [String] Binary data of the factbase (can be saved to file)
|
154
|
+
# @raise [ServerFailure] If the job doesn't exist or pull fails
|
146
155
|
def pull(id)
|
147
156
|
raise 'The ID of the job is nil' if id.nil?
|
148
157
|
raise 'The ID of the job must be a positive integer' unless id.positive?
|
@@ -175,10 +184,11 @@ class BazaRb
|
|
175
184
|
data
|
176
185
|
end
|
177
186
|
|
178
|
-
#
|
187
|
+
# Check if the job with this ID is finished already.
|
179
188
|
#
|
180
189
|
# @param [Integer] id The ID of the job on the server
|
181
|
-
# @return [Boolean] TRUE if the job
|
190
|
+
# @return [Boolean] TRUE if the job has completed execution, FALSE otherwise
|
191
|
+
# @raise [ServerFailure] If the job doesn't exist
|
182
192
|
def finished?(id)
|
183
193
|
raise 'The ID of the job is nil' if id.nil?
|
184
194
|
raise 'The ID of the job must be a positive integer' unless id.positive?
|
@@ -291,7 +301,7 @@ class BazaRb
|
|
291
301
|
)
|
292
302
|
end
|
293
303
|
throw :"Job name '#{name}' locked at #{@host}" if ret.code == 302
|
294
|
-
raise "Failed to lock '#{name}' job at #{@host}, it's
|
304
|
+
raise "Failed to lock '#{name}' job at #{@host}, it's already locked"
|
295
305
|
end
|
296
306
|
end
|
297
307
|
|
@@ -303,6 +313,7 @@ class BazaRb
|
|
303
313
|
raise 'The "name" of the job is nil' if name.nil?
|
304
314
|
raise 'The "name" of the job may not be empty' if name.empty?
|
305
315
|
raise 'The "owner" of the lock is nil' if owner.nil?
|
316
|
+
raise 'The "owner" of the lock may not be empty' if owner.empty?
|
306
317
|
elapsed(@loog) do
|
307
318
|
with_retries(max_tries: @retries, rescue: TimedOut) do
|
308
319
|
checked(
|
@@ -365,10 +376,12 @@ class BazaRb
|
|
365
376
|
exists
|
366
377
|
end
|
367
378
|
|
368
|
-
# Place a single durable.
|
379
|
+
# Place a single durable file on the server.
|
369
380
|
#
|
370
381
|
# @param [String] jname The name of the job on the server
|
371
|
-
# @param [String] file The file
|
382
|
+
# @param [String] file The path to the file to upload
|
383
|
+
# @return [Integer] The ID of the created durable
|
384
|
+
# @raise [ServerFailure] If the upload fails
|
372
385
|
def durable_place(jname, file)
|
373
386
|
raise 'The "jname" of the durable is nil' if jname.nil?
|
374
387
|
raise 'The "jname" of the durable may not be empty' if jname.empty?
|
@@ -503,13 +516,14 @@ class BazaRb
|
|
503
516
|
end
|
504
517
|
end
|
505
518
|
|
506
|
-
# Transfer
|
519
|
+
# Transfer funds to another user.
|
507
520
|
#
|
508
|
-
# @param [String] recipient GitHub
|
509
|
-
# @param [Float] amount The amount in
|
510
|
-
# @param [String] summary The description
|
511
|
-
# @param [Integer] job
|
512
|
-
# @return [Integer] Receipt ID
|
521
|
+
# @param [String] recipient GitHub username of the recipient (e.g. "yegor256")
|
522
|
+
# @param [Float] amount The amount to transfer in Ƶ (zents)
|
523
|
+
# @param [String] summary The description/reason for the payment
|
524
|
+
# @param [Integer] job Optional job ID to associate with this transfer
|
525
|
+
# @return [Integer] Receipt ID for the transaction
|
526
|
+
# @raise [ServerFailure] If the transfer fails
|
513
527
|
def transfer(recipient, amount, summary, job: nil)
|
514
528
|
raise 'The "recipient" is nil' if recipient.nil?
|
515
529
|
raise 'The "amount" is nil' if amount.nil?
|
@@ -538,18 +552,19 @@ class BazaRb
|
|
538
552
|
)
|
539
553
|
end
|
540
554
|
id = ret.headers['X-Zerocracy-ReceiptId'].to_i
|
541
|
-
throw :"Transferred
|
555
|
+
throw :"Transferred Ƶ#{format('%0.6f', amount)} to @#{recipient} at #{@host}"
|
542
556
|
end
|
543
557
|
id
|
544
558
|
end
|
545
559
|
|
546
|
-
# Pay fee
|
560
|
+
# Pay a fee associated with a job.
|
547
561
|
#
|
548
|
-
# @param [String] tab The
|
549
|
-
# @param [Float] amount The amount in
|
550
|
-
# @param [String] summary The description
|
551
|
-
# @param [Integer] job The ID of the job
|
552
|
-
# @return [Integer] Receipt ID
|
562
|
+
# @param [String] tab The category/type of the fee (use "unknown" if not sure)
|
563
|
+
# @param [Float] amount The fee amount in Ƶ (zents)
|
564
|
+
# @param [String] summary The description/reason for the fee
|
565
|
+
# @param [Integer] job The ID of the job this fee is for
|
566
|
+
# @return [Integer] Receipt ID for the fee payment
|
567
|
+
# @raise [ServerFailure] If the payment fails
|
553
568
|
def fee(tab, amount, summary, job)
|
554
569
|
raise 'The "tab" is nil' if tab.nil?
|
555
570
|
raise 'The "amount" is nil' if amount.nil?
|
@@ -580,40 +595,62 @@ class BazaRb
|
|
580
595
|
)
|
581
596
|
end
|
582
597
|
id = ret.headers['X-Zerocracy-ReceiptId'].to_i
|
583
|
-
throw :"Fee
|
598
|
+
throw :"Fee Ƶ#{format('%0.6f', amount)} paid at #{@host}"
|
584
599
|
end
|
585
600
|
id
|
586
601
|
end
|
587
602
|
|
588
|
-
# Pop job from the server.
|
603
|
+
# Pop the next available job from the server's queue.
|
589
604
|
#
|
590
|
-
# @param [String] owner
|
591
|
-
# @param [String] zip The path
|
592
|
-
# @return [Boolean] TRUE if job
|
605
|
+
# @param [String] owner Identifier of who is taking the job (any descriptive text)
|
606
|
+
# @param [String] zip The local file path where the job's ZIP archive will be saved
|
607
|
+
# @return [Boolean] TRUE if a job was successfully popped, FALSE if queue is empty
|
608
|
+
# @raise [ServerFailure] If the pop operation fails
|
593
609
|
def pop(owner, zip)
|
594
610
|
raise 'The "zip" of the job is nil' if zip.nil?
|
595
611
|
success = false
|
596
612
|
FileUtils.rm_f(zip)
|
613
|
+
job = nil
|
597
614
|
elapsed(@loog) do
|
598
|
-
File.open(zip, 'wb') do |f|
|
599
|
-
|
600
|
-
home.append('pop').add(owner:)
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
request.
|
615
|
+
File.open(zip, 'wb+') do |f|
|
616
|
+
loop do
|
617
|
+
uri = home.append('pop').add(owner:)
|
618
|
+
uri = uri.add(job:) if job
|
619
|
+
request = Typhoeus::Request.new(
|
620
|
+
uri.to_s,
|
621
|
+
method: :get,
|
622
|
+
headers: headers.merge(
|
623
|
+
'Accept' => 'application/octet-stream',
|
624
|
+
'Range' => "bytes=#{f.size}-"
|
625
|
+
),
|
626
|
+
connecttimeout: @timeout,
|
627
|
+
timeout: @timeout
|
628
|
+
)
|
629
|
+
request.on_body do |chunk|
|
630
|
+
f.write(chunk)
|
631
|
+
end
|
632
|
+
with_retries(max_tries: @retries, rescue: TimedOut) do
|
633
|
+
request.run
|
634
|
+
end
|
635
|
+
ret = request.response
|
636
|
+
checked(ret, [200, 204, 206])
|
637
|
+
success = ret.code != 204
|
638
|
+
break unless ret.code == 206
|
639
|
+
job = ret.headers['X-Zerocracy-JobId']
|
640
|
+
raise 'Job ID is not returned in X-Zerocracy-JobId' if job.nil?
|
641
|
+
raise "Job ID returned in X-Zerocracy-JobId is not valid (#{job.inspect})" unless job.match?(/^[0-9]+$/)
|
642
|
+
_, v = ret.headers['Content-Range'].split
|
643
|
+
range, total = v.split('/')
|
644
|
+
raise "Total size is not valid (#{total.inspect})" unless total.match?(/^\*|[0-9]+$/)
|
645
|
+
b, e = range.split('-')
|
646
|
+
raise "Range is not valid (#{range.inspect})" unless e.match?(/^[0-9]+$/)
|
647
|
+
len = ret.headers['Content-Length'].to_i
|
648
|
+
unless len.zero?
|
649
|
+
raise "Range size (#{range.inspect}) is not equal to Content-Length" unless len - 1 == e.to_i - b.to_i
|
650
|
+
raise "Range end (#{range.inspect}) is not equal to #{f.size}" if e.to_i != f.size - 1
|
651
|
+
end
|
652
|
+
break if e.to_i == total.to_i - 1
|
613
653
|
end
|
614
|
-
ret = request.response
|
615
|
-
checked(ret, [200, 204])
|
616
|
-
success = ret.code == 200
|
617
654
|
end
|
618
655
|
unless success
|
619
656
|
FileUtils.rm_f(zip)
|
@@ -624,10 +661,11 @@ class BazaRb
|
|
624
661
|
success
|
625
662
|
end
|
626
663
|
|
627
|
-
# Submit a ZIP archive to finish a job.
|
664
|
+
# Submit a ZIP archive to finish a previously popped job.
|
628
665
|
#
|
629
|
-
# @param [Integer] id The ID of the job
|
630
|
-
# @param [String] zip The path to the ZIP file
|
666
|
+
# @param [Integer] id The ID of the job to finish
|
667
|
+
# @param [String] zip The path to the ZIP file containing job results
|
668
|
+
# @raise [ServerFailure] If the submission fails
|
631
669
|
def finish(id, zip)
|
632
670
|
raise 'The ID of the job is nil' if id.nil?
|
633
671
|
raise 'The ID of the job must be a positive integer' unless id.positive?
|
@@ -652,13 +690,19 @@ class BazaRb
|
|
652
690
|
end
|
653
691
|
end
|
654
692
|
|
655
|
-
# Enter a valve.
|
693
|
+
# Enter a valve to cache or retrieve a computation result.
|
694
|
+
#
|
695
|
+
# Valves prevent duplicate computations by caching results. If a result
|
696
|
+
# for the given badge already exists, it's returned. Otherwise, the block
|
697
|
+
# is executed and its result is cached.
|
656
698
|
#
|
657
699
|
# @param [String] name Name of the job
|
658
|
-
# @param [String] badge Unique
|
659
|
-
# @param [String] why The reason
|
660
|
-
# @param [nil|Integer] job
|
661
|
-
# @
|
700
|
+
# @param [String] badge Unique identifier for this valve/computation
|
701
|
+
# @param [String] why The reason/description for entering this valve
|
702
|
+
# @param [nil|Integer] job Optional job ID to associate with this valve
|
703
|
+
# @yield Block that computes the result if not cached
|
704
|
+
# @return [String] The cached result or newly computed result from the block
|
705
|
+
# @raise [ServerFailure] If the valve operation fails
|
662
706
|
def enter(name, badge, why, job)
|
663
707
|
elapsed(@loog, intro: "Entered valve #{badge} to #{name}") do
|
664
708
|
with_retries(max_tries: @retries, rescue: TimedOut) do
|
@@ -692,8 +736,13 @@ class BazaRb
|
|
692
736
|
end
|
693
737
|
end
|
694
738
|
|
695
|
-
# Get CSRF token from the server.
|
696
|
-
#
|
739
|
+
# Get CSRF token from the server for authenticated requests.
|
740
|
+
#
|
741
|
+
# The CSRF token is required for POST requests to prevent cross-site
|
742
|
+
# request forgery attacks.
|
743
|
+
#
|
744
|
+
# @return [String] The CSRF token for the authenticated user
|
745
|
+
# @raise [ServerFailure] If token retrieval fails
|
697
746
|
def csrf
|
698
747
|
token = nil
|
699
748
|
elapsed(@loog) do
|
@@ -780,7 +829,7 @@ class BazaRb
|
|
780
829
|
case ret.code
|
781
830
|
when 500
|
782
831
|
msg +=
|
783
|
-
|
832
|
+
", most probably it's an internal error on the server, " \
|
784
833
|
'please report this to https://github.com/zerocracy/baza'
|
785
834
|
when 503
|
786
835
|
msg +=
|
@@ -788,7 +837,7 @@ class BazaRb
|
|
788
837
|
'please report this to https://github.com/zerocracy/baza.rb'
|
789
838
|
when 404
|
790
839
|
msg +=
|
791
|
-
|
840
|
+
", most probably you are trying to reach a wrong server, which doesn't " \
|
792
841
|
'have the URL that it is expected to have'
|
793
842
|
when 0
|
794
843
|
msg += ', most likely an internal error'
|
data/test/test__helper.rb
CHANGED
@@ -5,14 +5,28 @@
|
|
5
5
|
|
6
6
|
$stdout.sync = true
|
7
7
|
|
8
|
-
require 'minitest/reporters'
|
9
|
-
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
10
|
-
|
11
8
|
require 'simplecov'
|
12
|
-
SimpleCov.start
|
13
|
-
|
14
9
|
require 'simplecov-cobertura'
|
15
|
-
SimpleCov.
|
10
|
+
unless SimpleCov.running || ARGV.include?('--no-cov')
|
11
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
12
|
+
[
|
13
|
+
SimpleCov::Formatter::HTMLFormatter,
|
14
|
+
SimpleCov::Formatter::CoberturaFormatter
|
15
|
+
]
|
16
|
+
)
|
17
|
+
SimpleCov.minimum_coverage 95
|
18
|
+
SimpleCov.minimum_coverage_by_file 95
|
19
|
+
SimpleCov.start do
|
20
|
+
add_filter 'vendor/'
|
21
|
+
add_filter 'target/'
|
22
|
+
track_files 'judges/**/*.rb'
|
23
|
+
track_files 'lib/**/*.rb'
|
24
|
+
track_files '*.rb'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'minitest/reporters'
|
29
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
16
30
|
|
17
31
|
require 'webmock/minitest'
|
18
32
|
require 'minitest/autorun'
|