diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml new file mode 100644 index 0000000..079eaf1 --- /dev/null +++ b/.github/workflows/coverage.yaml @@ -0,0 +1,24 @@ +name: Test Coverage + +on: + workflow_call: + + workflow_dispatch: + +jobs: + SimpleCov: + runs-on: ubuntu-latest + steps: + # Only download these artifacts if they don't already exist + - if: ${{ hashFiles('coverage/.last_run.json') == '' }} + uses: actions/download-artifact@v4 + with: + name: coverage-last-run + path: coverage/ + + - name: Check SimpleCov coverage + uses: joshmfrankel/simplecov-check-action@main + with: + minimum_suite_coverage: 97 + minimum_file_coverage: 90 + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 016759e..47b1683 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -15,12 +15,25 @@ jobs: - '4.0.0' steps: - uses: actions/checkout@v4 + name: Checkout code + - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - bundler-cache: false + bundler-cache: true + - name: Install dependencies run: bundle install --jobs 4 --retry 3 - - name: Run the default task + + - name: Run the default rake task run: bundle exec rake + env: + COVERAGE: "true" + + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-last-run + path: coverage/ + include-hidden-files: true diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index bd9ea2c..148729c 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -10,3 +10,8 @@ jobs: validate_ruby: name: Ruby Tests uses: ./.github/workflows/main.yaml + + ensure_coverage: + needs: validate_ruby + name: Check SimpleCov coverage + uses: ./.github/workflows/coverage.yaml diff --git a/Gemfile b/Gemfile index 9488179..e205af3 100644 --- a/Gemfile +++ b/Gemfile @@ -13,3 +13,6 @@ gem 'rubocop' gem 'rubocop-minitest' gem 'rubocop-performance' gem 'rubocop-rake' +gem 'shoulda-context' +gem 'simplecov' +gem 'simplecov-json' diff --git a/Gemfile.lock b/Gemfile.lock index 27f4c9e..1ee3c0c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,6 +29,7 @@ GEM disposable (0.6.3) declarative (>= 0.0.9, < 1.0.0) representable (>= 3.1.1, < 4) + docile (1.4.1) dry-cli (1.3.0) dry-configurable (1.3.0) dry-core (~> 1.1) @@ -135,6 +136,16 @@ GEM ruby-progressbar (1.13.0) semantic_logger (4.17.0) concurrent-ruby (~> 1.0) + shoulda-context (2.0.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov-json (0.2.3) + json + simplecov + simplecov_json_formatter (0.1.4) trailblazer (2.1.3) trailblazer-activity-dsl-linear (>= 1.2.3, < 1.3.0) trailblazer-developer (>= 0.1.0, < 0.2.0) @@ -187,6 +198,9 @@ DEPENDENCIES rubocop-minitest rubocop-performance rubocop-rake + shoulda-context + simplecov + simplecov-json CHECKSUMS addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057 @@ -197,6 +211,7 @@ CHECKSUMS crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 disposable (0.6.3) sha256=7f2a3fb251bff6cd83f25b164043d4ec3531209b51b066ed476a9df9c2d384cc + docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e domeapi (0.0.2) dry-cli (1.3.0) sha256=984a715f9d7f8d9bf87b6530acdd4321dcf747636bfeb3ea7fd1b81bc0226e84 dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8 @@ -241,6 +256,11 @@ CHECKSUMS rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 semantic_logger (4.17.0) sha256=c54b60d8596abe2e22bd4f20648d4f6c46e4287fba357779c7932335c8d130e9 + shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 + simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 + simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246 + simplecov-json (0.2.3) sha256=c63823b5dfc95c85ff4cb94251765f08a166bdd68098f259368cbfe738e281f7 + simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 trailblazer (2.1.3) sha256=8adf157f552a4843de51456f7e70e28422799e5d060b265814d145774b8845b7 trailblazer-activity (0.17.0) sha256=2166bc8b3410b808ad2bfe00efaf559533d1b9a6fb0d704cf3bdad02cd6c0b88 trailblazer-activity-dsl-linear (1.2.6) sha256=42e4d3c9bd9469b65f9265fba74c703cf1a4117ea7f42a0a78112ebb3bab986f diff --git a/Rakefile b/Rakefile index fd157b8..4564da1 100644 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,13 @@ # frozen_string_literal: true require 'bundler/gem_tasks' -require 'minitest/test_task' +require 'rake/testtask' -Minitest::TestTask.create +Rake::TestTask.new do |t| + t.libs << 'test' + t.test_files = FileList['test/**/*_test.rb'] + t.ruby_opts = %w[-rhelper] +end require 'rubocop/rake_task' diff --git a/Readme.adoc b/Readme.adoc index 510aa23..2077216 100644 --- a/Readme.adoc +++ b/Readme.adoc @@ -102,6 +102,22 @@ filter = Rubyists::Domeapi::Polymarket::TradeHistory::Filter.new( trades = client.polymarket.trade_history.list(filter) ---- +==== Orderbook + +Get orderbook history: + +[source,ruby] +---- +filter = Rubyists::Domeapi::Polymarket::Orderbook::Filter.new( + Rubyists::Domeapi::Polymarket::Orderbook::Filter::Properties.new( + token_id: 'token_id', + start_time: 1625097600000, + end_time: 1625184000000 + ) +) +snapshots = client.polymarket.orderbook.list(filter) +---- + == Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. diff --git a/lib/domeapi/client.rb b/lib/domeapi/client.rb index 314a351..2a8eeb3 100644 --- a/lib/domeapi/client.rb +++ b/lib/domeapi/client.rb @@ -25,7 +25,7 @@ def get(path, params: {}) end def polymarket - @polymarket ||= Polymarket::Client.new(self) + @polymarket ||= Polymarket::Client.new(clone) end private diff --git a/lib/domeapi/contract.rb b/lib/domeapi/contract.rb index e000d1e..e7d9b87 100644 --- a/lib/domeapi/contract.rb +++ b/lib/domeapi/contract.rb @@ -9,6 +9,21 @@ module Domeapi class Contract < Reform::Form feature Reform::Form::Dry + # Custom class methods can be defined here + module ClassMethods + def propertize(*names) + # names is always an array because of the splat + names = names.flatten + const_set(:Properties, Struct.new(*custom_definitions, *names, keyword_init: true)) + names.each { |name| property name, populator: ->(value:, **) { value || skip! } } + end + end + + def self.inherited(subclass) + subclass.extend ClassMethods + super + end + def self.custom_definitions = instance_variable_get(:@definitions)&.keys&.map(&:to_sym) || [] def to_h diff --git a/lib/domeapi/polymarket.rb b/lib/domeapi/polymarket.rb index dd5eeee..43c3953 100644 --- a/lib/domeapi/polymarket.rb +++ b/lib/domeapi/polymarket.rb @@ -7,3 +7,14 @@ module Polymarket end end end + +require_relative 'polymarket/endpoint' +require_relative 'polymarket/listable' +require_relative 'polymarket/markets' +require_relative 'polymarket/candlesticks' +require_relative 'polymarket/trade_history' +require_relative 'polymarket/orderbook' +require_relative 'polymarket/activity' +require_relative 'polymarket/market_price' +require_relative 'polymarket/wallet' +require_relative 'polymarket/wallet_profit_and_loss' diff --git a/lib/domeapi/polymarket/activity.rb b/lib/domeapi/polymarket/activity.rb new file mode 100644 index 0000000..92df302 --- /dev/null +++ b/lib/domeapi/polymarket/activity.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Rubyists + module Domeapi + module Polymarket + # Activity API endpoints + class Activity < Endpoint + include Listable + + polymarket_path 'activity' + + # Filter for activity + class Filter < Contract + propertize(%i[user start_time end_time market_slug condition_id limit offset]) + + validation do + # :nocov: + params do + required(:user).filled(:string) + optional(:start_time).maybe(:integer) + optional(:end_time).maybe(:integer) + optional(:market_slug).maybe(:string) + optional(:condition_id).maybe(:string) + optional(:limit).maybe(:integer, gteq?: 1, lteq?: 1000) + optional(:offset).maybe(:integer, gteq?: 0) + end + # :nocov: + end + end + end + end + end +end diff --git a/lib/domeapi/polymarket/candlesticks.rb b/lib/domeapi/polymarket/candlesticks.rb index 2c4099a..69937ab 100644 --- a/lib/domeapi/polymarket/candlesticks.rb +++ b/lib/domeapi/polymarket/candlesticks.rb @@ -5,39 +5,26 @@ module Domeapi module Polymarket # Candlesticks API endpoints class Candlesticks < Endpoint + include Listable + + polymarket_path 'markets/get_candlesticks' + # Filter for listing candlesticks, # from https://docs.domeapi.io/api-reference/endpoint/get-candlesticks class Filter < Contract - Properties = Struct.new( - :condition_id, - :start_time, - :end_time, - :interval, - keyword_init: true - ) - - Properties.members.each { |member| property member, populator: ->(value:, **) { value || skip! } } + propertize(%i[condition_id start_time end_time interval]) validation do + # :nocov: params do required(:condition_id).filled(:string) required(:start_time).filled(:integer) required(:end_time).filled(:integer) optional(:interval).maybe(:integer, gteq?: 1, lteq?: 1440) end + # :nocov: end end - - # Get OHLC candlesticks - # - # @param filter [Filter] Filter options - # - # @return [Candlestick] The candlestick data - def list(filter = Filter.new(Filter::Properties.new)) - raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.valid? - - client.get('markets/get_candlesticks', params: filter.to_h) - end end end end diff --git a/lib/domeapi/polymarket/client.rb b/lib/domeapi/polymarket/client.rb index a7ac535..2d6a58d 100644 --- a/lib/domeapi/polymarket/client.rb +++ b/lib/domeapi/polymarket/client.rb @@ -27,6 +27,26 @@ def candlesticks def trade_history @trade_history ||= TradeHistory.new(client) end + + def orderbook + @orderbook ||= Orderbook.new(client) + end + + def activity + @activity ||= Activity.new(client) + end + + def market_price + @market_price ||= MarketPrice.new(client) + end + + def wallet + @wallet ||= Wallet.new(client) + end + + def wallet_profit_and_loss + @wallet_profit_and_loss ||= WalletProfitAndLoss.new(client) + end end end end diff --git a/lib/domeapi/polymarket/endpoint.rb b/lib/domeapi/polymarket/endpoint.rb index 6d09b7f..ac832b3 100644 --- a/lib/domeapi/polymarket/endpoint.rb +++ b/lib/domeapi/polymarket/endpoint.rb @@ -13,7 +13,7 @@ class Endpoint # # @return [void] def initialize(client = nil) - @client = client || ::Rubyists::Domeapi::Client.new + @client = client || Client.new end end end diff --git a/lib/domeapi/polymarket/listable.rb b/lib/domeapi/polymarket/listable.rb new file mode 100644 index 0000000..86fbab2 --- /dev/null +++ b/lib/domeapi/polymarket/listable.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Rubyists + module Domeapi + module Polymarket + # Mixin for listable resources + module Listable + def self.included(base) + base.extend(ClassMethods) + end + + # Class methods for Listable + module ClassMethods + attr_accessor :endpoint_path + + # Set the endpoint path for the resource + # + # @param path [String] API endpoint path + # + # @return [String] endpoint path + def polymarket_path(path) + self.endpoint_path = path + end + + def list(...) + new.list(...) + end + end + + # List resources + # + # @param filter [Filter|Hash] Filter options + # + # @return [Hash|Array] resource data + def list(filter = self.class::Filter.new(self.class::Filter::Properties.new)) + filter = self.class::Filter.new(self.class::Filter::Properties.new(**filter)) if filter.is_a?(Hash) + raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({}) + + client.get(self.class.endpoint_path, params: filter.to_h) + end + end + end + end +end diff --git a/lib/domeapi/polymarket/market_price.rb b/lib/domeapi/polymarket/market_price.rb new file mode 100644 index 0000000..18abe83 --- /dev/null +++ b/lib/domeapi/polymarket/market_price.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Rubyists + module Domeapi + module Polymarket + # Market Price API endpoints + class MarketPrice < Endpoint + include Listable + + polymarket_path 'markets/get_market_price' + + # Filter for market price + class Filter < Contract + propertize(%i[token_id at_time]) + + validation do + # :nocov: + params do + required(:token_id).filled(:string) + optional(:at_time).maybe(:integer) + end + # :nocov: + end + end + end + end + end +end diff --git a/lib/domeapi/polymarket/markets.rb b/lib/domeapi/polymarket/markets.rb index 2ff9ffd..d0b88c0 100644 --- a/lib/domeapi/polymarket/markets.rb +++ b/lib/domeapi/polymarket/markets.rb @@ -4,63 +4,26 @@ module Rubyists module Domeapi module Polymarket # Markets API endpoints - class Markets - attr_reader :client + class Markets < Endpoint + include Listable + + polymarket_path 'markets' # Filter for Polymarket markets, # from https://docs.domeapi.io/api-reference/endpoint/get-markets class Filter < Contract - Properties = Struct.new( - :market_slug, - :event_slug, - :condition_id, - :tags, - :status, - :min_volume, - :limit, - :offset, - :start_time, - :end_time, - keyword_init: true - ) - - # Define properties with custom populator to skip optional params with nil values - Properties.members.each do |member| - property member, populator: ->(value:, **) { value || skip! } - end + propertize(%i[market_slug event_slug condition_id tags status min_volume limit offset start_time end_time]) validation do + # :nocov: params do optional(:status).maybe(:string, included_in?: %w[open closed]) optional(:offset).maybe(:integer, gteq?: 0, lteq?: 100) end + # :nocov: end end - class << self - # @see #list - def list(...) - new.list(...) - end - end - - # @param client [Rubyists::Domeapi::Polymarket::Client] - # - # @return [void] - def initialize(client = Rubyists::Domeapi::Polymarket::Client.new) - @client = client - end - - # List markets - # @param filter [MarketFilter] Filter options - # - # @return [Array] list of markets - def list(filter = Filter.new(Filter::Properties.new)) - raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.valid? - - client.get('markets', params: filter.to_h) - end - # Fetch current or historical price for a market # # @param token_id [String] diff --git a/lib/domeapi/polymarket/orderbook.rb b/lib/domeapi/polymarket/orderbook.rb new file mode 100644 index 0000000..d3efa26 --- /dev/null +++ b/lib/domeapi/polymarket/orderbook.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Rubyists + module Domeapi + module Polymarket + # Orderbook API endpoints + class Orderbook < Endpoint + include Listable + + polymarket_path 'markets/get_orderbook_history' + + # Filter for orderbook history + class Filter < Contract + propertize(%i[token_id start_time end_time limit offset]) + + validation do + # :nocov: + params do + required(:token_id).filled(:string) + required(:start_time).filled(:integer) + required(:end_time).filled(:integer) + optional(:limit).maybe(:integer, gteq?: 1, lteq?: 1000) + optional(:offset).maybe(:integer, gteq?: 0) + end + # :nocov: + end + end + end + end + end +end diff --git a/lib/domeapi/polymarket/trade_history.rb b/lib/domeapi/polymarket/trade_history.rb index e9f7d46..dbe5d30 100644 --- a/lib/domeapi/polymarket/trade_history.rb +++ b/lib/domeapi/polymarket/trade_history.rb @@ -4,33 +4,19 @@ module Rubyists module Domeapi module Polymarket # Trade History API endpoints - class TradeHistory - attr_reader :client + class TradeHistory < Endpoint + include Listable - # @param client [Rubyists::Domeapi::Polymarket::Client] - # - # @return [void] - def initialize(client = Rubyists::Domeapi::Polymarket::Client.new) - @client = client - end + polymarket_path 'markets/get_trade_history' + + attr_reader :client # Filter for trade history class Filter < Contract - Properties = Struct.new( - :market_slug, - :condition_id, - :token_id, - :start_time, - :end_time, - :limit, - :offset, - :user, - keyword_init: true - ) - - Properties.members.each { |member| property member, populator: ->(model:, **) { model || skip! } } + propertize(%i[market_slug condition_id token_id start_time end_time limit offset user]) validation do + # :nocov: params do optional(:market_slug).maybe(:string) optional(:condition_id).maybe(:string) @@ -41,19 +27,9 @@ class Filter < Contract optional(:offset).maybe(:integer, gteq?: 0) optional(:user).maybe(:string) end + # :nocov: end end - - # Get trade history - # - # @param filter [Filter] Filter options - # - # @return [Hash] trade history data - def list(filter = Filter.new(Filter::Properties.new)) - raise ArgumentError, filter.errors.full_messages.join(', ') unless filter.validate({}) - - client.get('markets/get_trade_history', params: filter.to_h) - end end end end diff --git a/lib/domeapi/polymarket/wallet.rb b/lib/domeapi/polymarket/wallet.rb new file mode 100644 index 0000000..c0fd6b8 --- /dev/null +++ b/lib/domeapi/polymarket/wallet.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Rubyists + module Domeapi + module Polymarket + # Wallet API endpoints + class Wallet < Endpoint + include Listable + + polymarket_path 'wallet' + + # Filter for wallet + class Filter < Contract + propertize(%i[eoa proxy with_metrics start_time end_time]) + + validation do + # :nocov: + params do + optional(:eoa).maybe(:string) + optional(:proxy).maybe(:string) + optional(:with_metrics).maybe(:bool) + optional(:start_time).maybe(:integer) + optional(:end_time).maybe(:integer) + end + + rule(:eoa, :proxy) do + key.failure('Either eoa or proxy must be provided, but not both') if values[:eoa] && values[:proxy] + key.failure('Either eoa or proxy must be provided') if !values[:eoa] && !values[:proxy] + end + # :nocov: + end + end + end + end + end +end diff --git a/lib/domeapi/polymarket/wallet_profit_and_loss.rb b/lib/domeapi/polymarket/wallet_profit_and_loss.rb new file mode 100644 index 0000000..0fffd3d --- /dev/null +++ b/lib/domeapi/polymarket/wallet_profit_and_loss.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Rubyists + module Domeapi + module Polymarket + # Wallet Profit and Loss API endpoints + class WalletProfitAndLoss < Endpoint + include Listable + + polymarket_path 'wallet/pnl' + + # Filter for wallet pnl + class Filter < Contract + propertize(%i[wallet_address granularity start_time end_time]) + + validation do + # :nocov: + params do + required(:wallet_address).filled(:string) + required(:granularity).filled(:string, included_in?: %w[day week month year all]) + optional(:start_time).maybe(:integer) + optional(:end_time).maybe(:integer) + end + # :nocov: + end + end + end + end + end +end diff --git a/test/domeapi/client_test.rb b/test/domeapi/client_test.rb new file mode 100644 index 0000000..a783e1b --- /dev/null +++ b/test/domeapi/client_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative '../helper' + +describe Rubyists::Domeapi::Client do + let(:client) { Rubyists::Domeapi::Client.new } + + before do + Rubyists::Domeapi.configure do |config| + config.api_key = 'test_api_key' + end + end + + it 'handles API errors' do + stub_request(:get, 'https://api.domeapi.io/v1/error') + .to_return(status: 500, body: 'Internal Server Error') + + error = _ { client.get('error') }.must_raise Rubyists::Domeapi::Error + _(error.message).must_include 'API Error: 500' + _(error.message).must_include 'Internal Server Error' + end + + it 'constructs full url correctly' do + client.prefix = 'test' + # We can't easily test private methods, but we can test the effect via get + stub_request(:get, 'https://api.domeapi.io/v1/test/path') + .to_return(status: 200, body: '{}') + + client.get('path') + end + + it 'returns a polymarket client' do + _(client.polymarket).must_be_instance_of Rubyists::Domeapi::Polymarket::Client + end +end diff --git a/test/domeapi/contract_test.rb b/test/domeapi/contract_test.rb new file mode 100644 index 0000000..cc05e8d --- /dev/null +++ b/test/domeapi/contract_test.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require_relative '../helper' + +describe Rubyists::Domeapi::Contract do + describe '.propertize' do + class TestContract < Rubyists::Domeapi::Contract # rubocop:disable Lint/ConstantDefinitionInBlock + propertize %i[foo bar] + end + + it 'defines a Properties struct' do + assert TestContract.const_defined?(:Properties) + _(TestContract::Properties.members).must_equal %i[foo bar] + end + + it 'accepts splat arguments' do + klass = Class.new(Rubyists::Domeapi::Contract) do + propertize :foo, :bar + end + + _(klass::Properties.members).must_equal %i[foo bar] + end + + it 'uses keyword_init for the struct' do + _(TestContract::Properties.new(foo: 'bar').foo).must_equal 'bar' + assert_raises(ArgumentError) { TestContract::Properties.new('bar', 'baz') } + end + + it 'defines properties on the contract' do + contract = TestContract.new(TestContract::Properties.new(foo: 'baz')) + + _(contract.foo).must_equal 'baz' + end + + it 'skips nil values' do + contract = TestContract.new(TestContract::Properties.new(foo: 'baz', bar: nil)) + contract.validate({}) + + _(contract.to_h).must_equal({ 'foo' => 'baz' }) + end + + it 'includes existing definitions' do + klass = Class.new(Rubyists::Domeapi::Contract) do + property :existing + propertize :new_prop + end + + _(klass::Properties.members).must_include :existing + _(klass::Properties.members).must_include :new_prop + end + end +end diff --git a/test/domeapi/polymarket/activity_test.rb b/test/domeapi/polymarket/activity_test.rb new file mode 100644 index 0000000..89cef43 --- /dev/null +++ b/test/domeapi/polymarket/activity_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Client do + let(:client) { Rubyists::Domeapi::Client.new } + let(:activity) { client.polymarket.activity } + + before do + Rubyists::Domeapi.configure do |config| + config.api_key = 'test_api_key' + end + end + + describe 'Polymarket Activity' do + it 'gets activity' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/activity') + .with(query: { user: '0x123' }) + .to_return(status: 200, body: '{"activities": []}') + + response = activity.list(user: '0x123') + + _(response).must_equal({ activities: [] }) + end + + it 'validates required parameters' do + error = _ { activity.list({}) }.must_raise ArgumentError + _(error.message).must_include 'User must be filled' + end + end +end diff --git a/test/domeapi/polymarket/candlesticks_test.rb b/test/domeapi/polymarket/candlesticks_test.rb index 5feba32..58dd532 100644 --- a/test/domeapi/polymarket/candlesticks_test.rb +++ b/test/domeapi/polymarket/candlesticks_test.rb @@ -48,6 +48,16 @@ _(response).must_equal({ candlesticks: [] }) end + it 'gets candlesticks with hash filter' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets/get_candlesticks') + .with(query: { condition_id: 'abc', start_time: '100', end_time: '200' }) + .to_return(status: 200, body: '{"candlesticks": []}') + + response = candlesticks.list(condition_id: 'abc', start_time: 100, end_time: 200) + + _(response).must_equal({ candlesticks: [] }) + end + it 'validates required parameters' do filter = Rubyists::Domeapi::Polymarket::Candlesticks::Filter.new( Rubyists::Domeapi::Polymarket::Candlesticks::Filter::Properties.new diff --git a/test/domeapi/polymarket/client_test.rb b/test/domeapi/polymarket/client_test.rb new file mode 100644 index 0000000..7558363 --- /dev/null +++ b/test/domeapi/polymarket/client_test.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Polymarket::Client do + it 'initializes with default client' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.client).must_be_instance_of Rubyists::Domeapi::Client + end + + it 'sets prefix on initialization' do + client = Rubyists::Domeapi::Client.new + Rubyists::Domeapi::Polymarket::Client.new(client) + + _(client.prefix).must_equal 'polymarket' + end + + it 'delegates get to the client' do + mock_client = Object.new + class << mock_client + attr_accessor :prefix + + def get(path, params: {}) + { path: path, params: params } + end + end + + client = Rubyists::Domeapi::Polymarket::Client.new(mock_client) + result = client.get('path') + + _(mock_client.prefix).must_equal 'polymarket' + _(result).must_equal({ path: 'path', params: {} }) + end + + it 'initializes markets endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.markets).must_be_instance_of Rubyists::Domeapi::Polymarket::Markets + end + + it 'initializes candlesticks endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.candlesticks).must_be_instance_of Rubyists::Domeapi::Polymarket::Candlesticks + end + + it 'initializes trade_history endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.trade_history).must_be_instance_of Rubyists::Domeapi::Polymarket::TradeHistory + end + + it 'initializes orderbook endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.orderbook).must_be_instance_of Rubyists::Domeapi::Polymarket::Orderbook + end + + it 'initializes activity endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.activity).must_be_instance_of Rubyists::Domeapi::Polymarket::Activity + end + + it 'initializes market_price endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.market_price).must_be_instance_of Rubyists::Domeapi::Polymarket::MarketPrice + end + + it 'initializes wallet endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.wallet).must_be_instance_of Rubyists::Domeapi::Polymarket::Wallet + end + + it 'initializes wallet_profit_and_loss endpoint' do + client = Rubyists::Domeapi::Polymarket::Client.new + + _(client.wallet_profit_and_loss).must_be_instance_of Rubyists::Domeapi::Polymarket::WalletProfitAndLoss + end +end diff --git a/test/domeapi/polymarket/endpoint_test.rb b/test/domeapi/polymarket/endpoint_test.rb new file mode 100644 index 0000000..dbe6112 --- /dev/null +++ b/test/domeapi/polymarket/endpoint_test.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Polymarket::Endpoint do + it 'initializes with default client' do + endpoint = Rubyists::Domeapi::Polymarket::Endpoint.new + + _(endpoint.client).must_be_instance_of Rubyists::Domeapi::Polymarket::Client + end + + it 'initializes with provided client' do + client = Object.new + endpoint = Rubyists::Domeapi::Polymarket::Endpoint.new(client) + + _(endpoint.client).must_equal client + end +end diff --git a/test/domeapi/polymarket/listable_test.rb b/test/domeapi/polymarket/listable_test.rb new file mode 100644 index 0000000..b914c29 --- /dev/null +++ b/test/domeapi/polymarket/listable_test.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Polymarket::Listable do + # Test using Markets as it includes Listable + describe 'Class methods' do + it 'delegates list to instance' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets') + .to_return(status: 200, body: '[]') + + response = Rubyists::Domeapi::Polymarket::Markets.list + + _(response).must_equal [] + end + end +end diff --git a/test/domeapi/polymarket/market_price_test.rb b/test/domeapi/polymarket/market_price_test.rb new file mode 100644 index 0000000..9e4414e --- /dev/null +++ b/test/domeapi/polymarket/market_price_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Client do + let(:client) { Rubyists::Domeapi::Client.new } + let(:market_price) { client.polymarket.market_price } + + before do + Rubyists::Domeapi.configure do |config| + config.api_key = 'test_api_key' + end + end + + describe 'Polymarket Market Price' do + it 'gets market price' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets/get_market_price') + .with(query: { token_id: '123' }) + .to_return(status: 200, body: '{"price": 0.5}') + + response = market_price.list(token_id: '123') + + _(response).must_equal({ price: 0.5 }) + end + + it 'validates required parameters' do + error = _ { market_price.list({}) }.must_raise ArgumentError + _(error.message).must_include 'Token Id must be filled' + end + end +end diff --git a/test/domeapi/polymarket/markets_test.rb b/test/domeapi/polymarket/markets_test.rb index 3c0c2f4..e332ee9 100644 --- a/test/domeapi/polymarket/markets_test.rb +++ b/test/domeapi/polymarket/markets_test.rb @@ -29,6 +29,16 @@ _(response).must_equal({ price: 0.5 }) end + it 'gets market price with at_time' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets/get_market_price') + .with(query: { token_id: '123', at_time: '1625097600' }) + .to_return(status: 200, body: '{"price": 0.4}') + + response = markets.price(token_id: '123', at_time: 1_625_097_600) + + _(response).must_equal({ price: 0.4 }) + end + it 'lists markets' do stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets') .with(query: { limit: '10', offset: '0' }) @@ -50,5 +60,21 @@ _(response).must_equal([{ id: 'market1' }]) end + + it 'lists markets with hash filter' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets') + .with(query: { tags: 'nfl' }) + .to_return(status: 200, body: '[{"id": "market1"}]') + + response = markets.list(tags: 'nfl') + + _(response).must_equal([{ id: 'market1' }]) + end + + it 'initializes with default client' do + markets = Rubyists::Domeapi::Polymarket::Markets.new + + _(markets.client).must_be_instance_of Rubyists::Domeapi::Polymarket::Client + end end end diff --git a/test/domeapi/polymarket/orderbook_test.rb b/test/domeapi/polymarket/orderbook_test.rb new file mode 100644 index 0000000..953d96c --- /dev/null +++ b/test/domeapi/polymarket/orderbook_test.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Client do + let(:client) { Rubyists::Domeapi::Client.new } + let(:orderbook) { client.polymarket.orderbook } + + before do + Rubyists::Domeapi.configure do |config| + config.api_key = 'test_api_key' + end + end + + describe 'Polymarket Orderbook' do + it 'gets orderbook history' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets/get_orderbook_history') + .with(query: { token_id: '123', start_time: '1000', end_time: '2000' }) + .to_return(status: 200, body: '{"snapshots": []}') + + filter = Rubyists::Domeapi::Polymarket::Orderbook::Filter.new( + Rubyists::Domeapi::Polymarket::Orderbook::Filter::Properties.new( + token_id: '123', + start_time: 1000, + end_time: 2000 + ) + ) + response = orderbook.list(filter) + + _(response).must_equal({ snapshots: [] }) + end + + it 'validates required parameters' do + filter = Rubyists::Domeapi::Polymarket::Orderbook::Filter.new( + Rubyists::Domeapi::Polymarket::Orderbook::Filter::Properties.new + ) + + error = _ { orderbook.list(filter) }.must_raise ArgumentError + _(error.message).must_include 'Token Id must be filled' + _(error.message).must_include 'Start Time must be filled' + _(error.message).must_include 'End Time must be filled' + end + + it 'validates limit range' do + filter = Rubyists::Domeapi::Polymarket::Orderbook::Filter.new( + Rubyists::Domeapi::Polymarket::Orderbook::Filter::Properties.new( + token_id: '123', + start_time: 1000, + end_time: 2000, + limit: 1001 + ) + ) + + error = _ { orderbook.list(filter) }.must_raise ArgumentError + _(error.message).must_include 'Limit must be less than or equal to 1000' + end + end +end diff --git a/test/domeapi/polymarket/trade_history_test.rb b/test/domeapi/polymarket/trade_history_test.rb index d210086..fe25220 100644 --- a/test/domeapi/polymarket/trade_history_test.rb +++ b/test/domeapi/polymarket/trade_history_test.rb @@ -29,6 +29,16 @@ _(response).must_equal({ trades: [] }) end + it 'gets trade history with hash filter' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/markets/get_trade_history') + .with(query: { market_slug: 'slug', limit: '10' }) + .to_return(status: 200, body: '{"trades": []}') + + response = trade_history.list(market_slug: 'slug', limit: 10) + + _(response).must_equal({ trades: [] }) + end + it 'validates limit range' do filter = Rubyists::Domeapi::Polymarket::TradeHistory::Filter.new( Rubyists::Domeapi::Polymarket::TradeHistory::Filter::Properties.new(limit: 1001) diff --git a/test/domeapi/polymarket/wallet_profit_and_loss_test.rb b/test/domeapi/polymarket/wallet_profit_and_loss_test.rb new file mode 100644 index 0000000..e0f7bbd --- /dev/null +++ b/test/domeapi/polymarket/wallet_profit_and_loss_test.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Client do + let(:client) { Rubyists::Domeapi::Client.new } + let(:wallet_pnl) { client.polymarket.wallet_profit_and_loss } + + before do + Rubyists::Domeapi.configure do |config| + config.api_key = 'test_api_key' + end + end + + describe 'Polymarket Wallet Profit and Loss' do + it 'gets wallet pnl' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/wallet/pnl') + .with(query: { wallet_address: '0x123', granularity: 'day' }) + .to_return(status: 200, body: '{"pnl_over_time": []}') + + response = wallet_pnl.list(wallet_address: '0x123', granularity: 'day') + + _(response).must_equal({ pnl_over_time: [] }) + end + + it 'validates required parameters' do + error = _ { wallet_pnl.list({}) }.must_raise ArgumentError + _(error.message).must_include 'Wallet Address must be filled' + _(error.message).must_include 'Granularity must be filled' + end + + it 'validates granularity' do + error = _ { wallet_pnl.list(wallet_address: '0x123', granularity: 'invalid') }.must_raise ArgumentError + _(error.message).must_include 'Granularity must be one of: day, week, month, year, all' + end + end +end diff --git a/test/domeapi/polymarket/wallet_test.rb b/test/domeapi/polymarket/wallet_test.rb new file mode 100644 index 0000000..e0bd348 --- /dev/null +++ b/test/domeapi/polymarket/wallet_test.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require_relative '../../helper' + +describe Rubyists::Domeapi::Client do + let(:client) { Rubyists::Domeapi::Client.new } + let(:wallet) { client.polymarket.wallet } + + before do + Rubyists::Domeapi.configure do |config| + config.api_key = 'test_api_key' + end + end + + describe 'Polymarket Wallet' do + it 'gets wallet with eoa' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/wallet') + .with(query: { eoa: '0x123' }) + .to_return(status: 200, body: '{"eoa": "0x123"}') + + response = wallet.list(eoa: '0x123') + + _(response).must_equal({ eoa: '0x123' }) + end + + it 'gets wallet with proxy' do + stub_request(:get, 'https://api.domeapi.io/v1/polymarket/wallet') + .with(query: { proxy: '0x456' }) + .to_return(status: 200, body: '{"proxy": "0x456"}') + + response = wallet.list(proxy: '0x456') + + _(response).must_equal({ proxy: '0x456' }) + end + + it 'validates either eoa or proxy' do + error = _ { wallet.list({}) }.must_raise ArgumentError + _(error.message).must_include 'Either eoa or proxy must be provided' + + error = _ { wallet.list(eoa: '0x123', proxy: '0x456') }.must_raise ArgumentError + _(error.message).must_include 'Either eoa or proxy must be provided, but not both' + end + end +end diff --git a/test/domeapi_test.rb b/test/domeapi_test.rb index 93e3cea..c8e443e 100644 --- a/test/domeapi_test.rb +++ b/test/domeapi_test.rb @@ -11,6 +11,10 @@ class DomeapiTest < Minitest::Spec refute_nil subject::VERSION end + it 'returns a default client' do + _(subject.client).must_be_instance_of Rubyists::Domeapi::Client + end + describe 'VERSION' do subject { ::Rubyists::Domeapi::VERSION } diff --git a/test/helper.rb b/test/helper.rb index 872d038..a5dea57 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,8 +1,26 @@ # frozen_string_literal: true +if ENV['COVERAGE'] + require 'simplecov' + require 'simplecov-json' + SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new( + [ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::JSONFormatter + ] + ) + SimpleCov.start do + enable_coverage :branch + add_filter '/test/' + add_filter '/vendor/' + end +end + require_relative '../lib/domeapi' require 'minitest/autorun' +require 'minitest/spec' +require 'shoulda/context' require 'webmock/minitest' require 'httpx' require 'httpx/adapters/webmock'