diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml new file mode 100644 index 0000000..2e76b07 --- /dev/null +++ b/.github/workflows/rspec.yml @@ -0,0 +1,19 @@ +name: RSpec + +on: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run RSpec + run: bundle exec rspec diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..fcdb2e1 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +4.0.0 diff --git a/Gemfile.lock b/Gemfile.lock index f9b5b09..6f4846b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,17 +8,25 @@ PATH GEM remote: https://rubygems.org/ specs: + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) bigdecimal (4.0.1) + crack (1.0.1) + bigdecimal + rexml csv (3.3.5) diff-lcs (1.6.2) + hashdiff (1.2.1) httparty (0.24.0) csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - json (2.18.0) + json (2.19.2) mini_mime (1.1.5) multi_xml (0.8.0) bigdecimal (>= 3.1, < 5) + public_suffix (7.0.5) + rexml (3.4.4) rspec (3.13.2) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -32,6 +40,10 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) + webmock (3.26.2) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) PLATFORMS arm64-darwin-25 @@ -40,6 +52,7 @@ PLATFORMS DEPENDENCIES rspec twitch! + webmock BUNDLED WITH 2.7.2 diff --git a/lib/twitch/client.rb b/lib/twitch/client.rb index 739a3e6..e4adec3 100644 --- a/lib/twitch/client.rb +++ b/lib/twitch/client.rb @@ -31,7 +31,7 @@ def adapter=(adapter) def link scope = "" @scope.each { |s| scope += s + '+' } - "#{@base_url}/oauth2/authorize?response_type=code&client_id=#{@client_id}&redirect_uri=#{@redirect_uri}&scope=#{scope}" + "https://id.twitch.tv/oauth2/authorize?response_type=code&client_id=#{@client_id}&redirect_uri=#{@redirect_uri}&scope=#{scope}" end def app_auth diff --git a/spec/helpers/open_uri_adapter.rb b/spec/helpers/open_uri_adapter.rb index 9b78892..ff817ec 100644 --- a/spec/helpers/open_uri_adapter.rb +++ b/spec/helpers/open_uri_adapter.rb @@ -7,7 +7,7 @@ def self.request(method, url, options={}) if (method == :get) ret = {} - open(url) do |io| + URI.open(url) do |io| ret[:body] = JSON.parse(io.read) ret[:response] = io.status.first.to_i end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d3f7dcf..f150f7b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,5 @@ require 'rspec' +require 'webmock/rspec' require 'twitch' RSpec.configure do |config| diff --git a/spec/twitch_spec.rb b/spec/twitch_spec.rb index 941a88f..6008217 100644 --- a/spec/twitch_spec.rb +++ b/spec/twitch_spec.rb @@ -3,219 +3,229 @@ describe Twitch do before(:each) do - @client_id = "" - @secret_key = "" - @redirect_uri = "http://localhost:3000/auth" - @scope = ["user_read", "channel_read", "channel_editor", "channel_commercial", "channel_stream", "user_blocks_edit"] - @scope_str = "" - @scope.each{ |s| @scope_str += s + "+" } - @access_token = "" - end + stub_request(:any, /api\.twitch\.tv/).to_return do |request| + body = if request.uri.path =~ /\/streams\/featured/ + if request.uri.query.to_s =~ /limit=100/ + JSON.generate({ "featured" => Array.new(26, {}) }) + else + JSON.generate({ "featured" => Array.new(25, {}) }) + end + else + '{}' + end + # /channels/followed returns 404 when the user is not following + status = request.uri.path =~ /\/channels\/followed/ ? 404 : 200 + { status: status, body: body, headers: { 'Content-Type' => 'application/json' } } + end + end + + let(:client) { Twitch.new } it 'should build accurate link' do - @t = Twitch.new({ - :client_id => @client_id, - :secret_key => @secret_key, - :redirect_uri => @redirect_uri, - :scope => ["user_read", "channel_read", "channel_editor", "channel_commercial", "channel_stream", "user_blocks_edit"] - }) - expect( @t.link ).to eq "https://api.twitch.tv/kraken/oauth2/authorize?response_type=code&client_id=#{@client_id}&redirect_uri=#{@redirect_uri}&scope=#{@scope_str}" + t = Twitch.new( + client_id: "abc", + redirect_uri: "http://localhost:3000/auth", + scope: ["user_read", "channel_read"] + ) + expect(t.link).to eq "https://id.twitch.tv/oauth2/authorize?response_type=code&client_id=abc&redirect_uri=http://localhost:3000/auth&scope=user_read+channel_read+" end - it 'should get user (not authenticated)' do - @t = Twitch.new() - expect( @t.user("day9")[:response] ).to eq 200 + # User + + it 'should get user by login name' do + client.user("day9") + expect(a_request(:get, "https://api.twitch.tv/helix/users/day9")).to have_been_made end - it 'should get user (authenticated)' do - @t = Twitch.new({:access_token => @access_token}) - expect( @t.user("day9")[:response] ).to eq 200 unless @access_token.empty? + it 'should not fetch authenticated user when unauthenticated' do + expect(client.user).to eq false end - it 'should get authenticated user' do - @t = Twitch.new({:access_token => @access_token}) - expect( @t.user()[:response] ).to eq 200 unless @access_token.empty? + it 'should get user when authenticated' do + skip 'requires a valid access token' end - it 'should not get authenticated user when not authenticated' do - @t = Twitch.new() - expect( @t.user() ).to eq false + it 'should get authenticated user' do + skip 'requires a valid access token' end + # Teams + it 'should get all teams' do - @t = Twitch.new() - expect( @t.teams()[:response] ).to eq 200 + client.teams + expect(a_request(:get, "https://api.twitch.tv/helix/teams/")).to have_been_made end it 'should get single team' do - @t = Twitch.new() - expect( @t.team("eg")[:response] ).to eq 200 + client.team("eg") + expect(a_request(:get, "https://api.twitch.tv/helix/teams/eg")).to have_been_made end + # Channel + it 'should get single channel' do - @t = Twitch.new() - expect( @t.channel("day9tv")[:response] ).to eq 200 + client.channel("day9tv") + expect(a_request(:get, "https://api.twitch.tv/helix/channels/day9tv")).to have_been_made + end + + it 'should get channel panels from the alt API base' do + client.channel_panels("esl_csgo") + expect(a_request(:get, "https://api.twitch.tv/api/channels/esl_csgo/panels")).to have_been_made end - it 'should get channel panels' do - @t = Twitch.new() - expect( @t.channel_panels("esl_csgo")[:response] ).to eq 200 + it 'should not fetch own channel without an access token' do + expect(client.channel).to eq false end it 'should get your channel' do - @t = Twitch.new({:access_token => @access_token}) - expect( @t.channel()[:response] ).to eq 200 unless @access_token.empty? + skip 'requires a valid access token' end it 'should edit your channel' do - @t = Twitch.new({:access_token => @access_token}) - expect( @t.edit_channel("Changing API", "Diablo III")[:response] ).to eq 200 unless @access_token.empty? + skip 'requires a valid access token' end - # it 'should run a comercial on your channel' do - # @t = Twitch.new({:access_token => @access_token}) - # expect( @t.runCommercial("dustinlakin")[:response] ).to eq 204 - # end + # Streams it 'should get a single user stream' do - @t = Twitch.new() - expect( @t.stream("nasltv")[:response] ).to eq 200 + client.stream("nasltv") + expect(a_request(:get, "https://api.twitch.tv/helix/streams/nasltv")).to have_been_made end it 'should get all streams' do - @t = Twitch.new() - expect( @t.streams()[:response] ).to eq 200 + client.streams + expect(a_request(:get, "https://api.twitch.tv/helix/streams")).to have_been_made end - it 'should get League of Legends streams with +' do - @t = Twitch.new() - expect( @t.streams({:game => "League+of+Legends"})[:response] ).to eq 200 + it 'should encode spaces as + in stream game filter' do + client.streams(game: "League of Legends") + expect(a_request(:get, "https://api.twitch.tv/helix/streams?game=League+of+Legends")).to have_been_made end - it 'should get League of Legends streams with spaces' do - @t = Twitch.new() - expect( @t.streams({:game => "League of Legends"})[:response] ).to eq 200 + it 'should pass pre-encoded + through in stream game filter' do + client.streams(game: "League+of+Legends") + expect(a_request(:get, "https://api.twitch.tv/helix/streams?game=League+of+Legends")).to have_been_made end it 'should get featured streams' do - @t = Twitch.new() - res = @t.featured_streams() + res = client.featured_streams + expect(a_request(:get, "https://api.twitch.tv/helix/streams/featured")).to have_been_made + expect(res[:body]["featured"].length).to eq 25 + end - expect(res[:response] ).to eq 200 - expect(res[:body]["featured"].length ).to eq 25 + it 'should pass options to featured streams' do + res = client.featured_streams(limit: 100) + expect(a_request(:get, "https://api.twitch.tv/helix/streams/featured?limit=100")).to have_been_made + expect(res[:body]["featured"].length).to be > 25 + end + + it 'should not fetch followed streams without an access token' do + expect(client.followed_streams).to eq false end - it 'should get more featured streams' do - @t = Twitch.new() - res = @t.featured_streams({:limit => 100}) + # Games - expect(res[:response] ).to eq 200 - expect(res[:body]["featured"].length).to be > 25 + it 'should get top games' do + client.top_games + expect(a_request(:get, "https://api.twitch.tv/helix/games/top")).to have_been_made end + # Chat + it 'should get chat links' do - @t = Twitch.new() - expect( @t.chat_links("day9tv")[:response] ).to eq 200 + client.chat_links("day9tv") + expect(a_request(:get, "https://api.twitch.tv/helix/chat/day9tv")).to have_been_made end it 'should get chat badges' do - @t = Twitch.new() - expect( @t.badges("day9tv")[:response] ).to eq 200 + client.badges("day9tv") + expect(a_request(:get, "https://api.twitch.tv/helix/chat/day9tv/badges")).to have_been_made end it 'should get chat emoticons' do - @t = Twitch.new() - expect( @t.emoticons()[:response] ).to eq 200 + client.emoticons + expect(a_request(:get, "https://api.twitch.tv/helix/chat/emoticons")).to have_been_made end - it 'should get channel followers' do - @t = Twitch.new() - expect( @t.following("day9tv")[:response] ).to eq 200 - end + # Follows - it 'should get channel followers with page 2' do - @t = Twitch.new() - expect( @t.following("day9tv", offset: 25, limit: 25)[:response] ).to eq 200 + it 'should get channel followers using from_id' do + client.following("day9tv") + expect(a_request(:get, "https://api.twitch.tv/helix/users/follows?from_id=day9tv")).to have_been_made end - it 'should get channels followed by user' do - @t = Twitch.new() - expect( @t.followed("day9")[:response] ).to eq 200 + it 'should paginate channel followers' do + client.following("day9tv", offset: 25, limit: 25) + expect(a_request(:get, "https://api.twitch.tv/helix/users/follows?offset=25&limit=25&from_id=day9tv")).to have_been_made end - it 'should get channels followed by user with page 2' do - @t = Twitch.new() - expect( @t.followed("day9", offset: 25, limit: 25)[:response] ).to eq 200 + it 'should get channels followed by user using to_id' do + client.followed("day9") + expect(a_request(:get, "https://api.twitch.tv/helix/users/follows?to_id=day9")).to have_been_made end - it 'should get status of user following channel' do - @t = Twitch.new() - expect( @t.follow_status("day9", "day9tv")[:response] ).to eq 404 + it 'should paginate channels followed by user' do + client.followed("day9", offset: 25, limit: 25) + expect(a_request(:get, "https://api.twitch.tv/helix/users/follows?offset=25&limit=25&to_id=day9")).to have_been_made end - it 'should get ingests' do - @t = Twitch.new() - expect( @t.ingests[:response] ).to eq 200 + it 'should return 404 when user does not follow channel' do + res = client.follow_status("day9", "day9tv") + expect(a_request(:get, "https://api.twitch.tv/helix/channels/followed?user_id=day9&broadcaster_id=day9tv")).to have_been_made + expect(res[:response]).to eq 404 end - it 'should get root' do - @t = Twitch.new() - expect( @t.root[:response] ).to eq 200 + it 'should not fetch followed videos without an access token' do + expect(client.followed_videos).to eq false end - it 'should get your followed streams' do - @t = Twitch.new() - expect( @t.followed_streams() ).to eq false - end + # Videos - it 'should get your followed videos' do - @t = Twitch.new() - expect( @t.followed_videos() ).to eq false + it 'should get top videos' do + client.top_videos + expect(a_request(:get, "https://api.twitch.tv/helix/videos/top")).to have_been_made end - it 'should get top games' do - @t = Twitch.new() - expect( @t.top_games[:response] ).to eq 200 + # Misc + + it 'should get ingests' do + client.ingests + expect(a_request(:get, "https://api.twitch.tv/helix/ingests")).to have_been_made end - it 'should get top videos' do - @t = Twitch.new() - expect( @t.top_videos[:response] ).to eq 200 + it 'should get root' do + client.root + expect(a_request(:get, "https://api.twitch.tv/helix/")).to have_been_made end + # Adapter + it 'should have a default adapter' do - @t = Twitch.new - expect( @t.adapter ).to eq(Twitch::Adapters::HTTPartyAdapter) + expect(Twitch.new.adapter).to eq(Twitch::Adapters::HTTPartyAdapter) end - it 'should work with a different adapter (open-uri).' do + it 'should work with a different adapter (open-uri)' do require 'helpers/open_uri_adapter' - - @t = Twitch.new adapter: Twitch::Adapters::OpenURIAdapter - - res = @t.featured_streams - - expect( res[:response] ).to eq 200 - expect( res[:body]["featured"].length ).to eq 25 + t = Twitch.new adapter: Twitch::Adapters::OpenURIAdapter + res = t.featured_streams + expect(res[:response]).to eq 200 + expect(res[:body]["featured"].length).to eq 25 end - it "should fall-back to the default adapter when passed an invalid adapter" do - expect( Twitch.new( adapter: false ).adapter ).to eq( Twitch::Adapters::DEFAULT_ADAPTER ) - expect( Twitch.new( adapter: 100 ).adapter ).to eq( Twitch::Adapters::DEFAULT_ADAPTER ) - expect( Twitch.new( adapter: :bad_constant ).adapter ).to eq( Twitch::Adapters::DEFAULT_ADAPTER ) + it 'should fall back to the default adapter when passed an invalid adapter' do + expect(Twitch.new(adapter: false ).adapter).to eq(Twitch::Adapters::DEFAULT_ADAPTER) + expect(Twitch.new(adapter: 100 ).adapter).to eq(Twitch::Adapters::DEFAULT_ADAPTER) + expect(Twitch.new(adapter: :bad_constant).adapter).to eq(Twitch::Adapters::DEFAULT_ADAPTER) - @t = Twitch.new - @t.adapter = nil - - expect( @t.adapter ).to eq( Twitch::Adapters::DEFAULT_ADAPTER ) + t = Twitch.new + t.adapter = nil + expect(t.adapter).to eq(Twitch::Adapters::DEFAULT_ADAPTER) end it 'should provide required Client-Id header each request' do - require 'helpers/debug_httparty_adapter' - $debug_output = StringIO.new - - Twitch.new(adapter: Twitch::Adapters::DebugHTTPartyAdapter, client_id: "test").featured_streams - - expect($debug_output.string).to match(/Client-Id: test/) + Twitch.new(client_id: "test").featured_streams + expect(a_request(:get, "https://api.twitch.tv/helix/streams/featured"). + with(headers: { 'Client-ID' => 'test' })).to have_been_made end -end \ No newline at end of file + +end diff --git a/twitch.gemspec b/twitch.gemspec index 5a6a7fc..5c7ef6f 100644 --- a/twitch.gemspec +++ b/twitch.gemspec @@ -19,4 +19,5 @@ Gem::Specification.new do |s| s.add_dependency('httparty') s.add_dependency('json') s.add_development_dependency('rspec') + s.add_development_dependency('webmock') end