├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── jwplayer-api-client.gemspec ├── lib └── jwplayer │ └── api │ ├── client.rb │ └── client │ └── version.rb └── spec ├── jwplayer └── api │ └── client_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.idea/ 3 | /.yardoc 4 | /Gemfile.lock 5 | /_yardoc/ 6 | /coverage/ 7 | /doc/ 8 | /pkg/ 9 | /spec/reports/ 10 | /tmp/ 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.0 4 | before_install: gem install bundler -v 1.11.2 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in jwplayer-api-client.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 raphi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JWPlayer::API::Client [![Build Status](https://travis-ci.org/raphi/jwplayer-api-client.svg?branch=master)](https://travis-ci.org/raphi/jwplayer-api-client) 2 | 3 | This gem aims to easily sign JWPlayer Platform API URLs according to the documentation: https://developer.jwplayer.com/jw-platform/reference/v1/authentication.html 4 | It is not intended to actually send the request but simply to generate the correctly signed URL. An example at the end of this documentation is provided though. 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | ```ruby 11 | gem 'jwplayer-api-client' 12 | ``` 13 | 14 | And then execute: 15 | 16 | $ bundle 17 | 18 | Or install it yourself as: 19 | 20 | $ gem install jwplayer-api-client 21 | 22 | ## Usage 23 | 24 | To get started, instantiate a new client: 25 | 26 | ```ruby 27 | irb> client = JWPlayer::API::Client.new(key: 'y0c6CFQ5', secret: 'YZWQ1SfmpFYEfW9kiR1QerRF') 28 | => #"api.jwplatform.com", :scheme=>"https", :version=>:v1, :key=>"y0c6CFQ5", :secret=>"YZWQ1SfmpFYEfW9kiR1QerRF", :format=>:json}> 29 | ``` 30 | 31 | If you have previously set `JWPLAYER_API_KEY` and `JWPLAYER_API_SECRET` ENV variables, you can simply do: 32 | 33 | ```ruby 34 | irb> client = JWPlayer::API::Client.new 35 | => #"api.jwplatform.com", :scheme=>"https", :version=>:v1, :key=>"y0c6CFQ5", :secret=>"YZWQ1SfmpFYEfW9kiR1QerRF", :format=>:json}> 36 | ``` 37 | 38 | `JWPlayer::API::Client.new()` accepts the following optional parameters: 39 | 40 | | Name | Default | Description | 41 | |-----------|---------------------------------------|-------------| 42 | | key | ENV['JWPLAYER_API_KEY'] | JWPlayer Platform API key 43 | | secret | ENV['JWPLAYER_API_SECRET'] | JWPlayer Platform API secret 44 | | host | 'api.jwplatform.com' | API host 45 | | scheme | 'https' | API scheme 46 | | version | :v1 | API version 47 | | format | :json | API response format 48 | | timestamp | current time | API UNIX timestamp used against replay-attacks 49 | | nonce | automatically generated for each call | 8 digit random number 50 | See https://developer.jwplayer.com/jw-platform/reference/v1/call_syntax.html for more information. 51 | 52 | Then, you can get a signed uri or signed url like this: 53 | 54 | ```ruby 55 | irb> client.signed_uri('videos/create') 56 | => # 57 | 58 | irb> client.signed_url('videos/create') 59 | => "https://api.jwplatform.com/v1/videos/create?api_format=json&api_key=y0c6CFQ5&api_nonce=36581160&api_signature=95c92965a690119b086e40e37c2bb9d9ef6d3781&api_timestamp=1462808317" 60 | ``` 61 | 62 | And with query parameters: 63 | 64 | ```ruby 65 | irb> client.signed_url('videos/create', title: 'My Super Video', description: 'This is cool') 66 | => "https://api.jwplatform.com/v1/videos/create?api_format=json&api_key=y0b9GFQ3&api_nonce=36581160&api_signature=4b2e1d7c6aeda3c87e634300563159a5ba99b661&api_timestamp=1462808317&description=This%20is%20cool&title=My%20Super%20Video" 67 | ``` 68 | 69 | ### IRL example 70 | 71 | Create a video reference in your JWPlayer Dashboard and get the `media_id`: 72 | 73 | ```ruby 74 | require 'typhoeus' 75 | 76 | data = { 77 | author: 'Raphaël', 78 | date: Date.new(2002,03,04).to_time.to_i, 79 | description: 'Yet Another Keynote', 80 | title: 'Apple Keynote', 81 | sourceformat: :m3u8, 82 | sourcetype: :url, 83 | sourceurl: 'http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8' 84 | } 85 | 86 | # Call JWPlayer /videos/create API https://developer.jwplayer.com/jw-platform/reference/v1/methods/videos/create.html 87 | jw_client = JWPlayer::API::Client.new 88 | signed_url = jw_client.signed_url('videos/create', data) 89 | response = Typhoeus.post(signed_url) 90 | json = JSON.parse(response.body) 91 | media_id = json.dig('video', 'key') 92 | ``` 93 | 94 | ## Development 95 | 96 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 97 | 98 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 99 | 100 | ## Contributing 101 | 102 | Bug reports and pull requests are welcome on GitHub at https://github.com/raphi/jwplayer-api-client. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 103 | 104 | 105 | ## License 106 | 107 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 108 | 109 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'jwplayer/api/client' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /jwplayer-api-client.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'jwplayer/api/client/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'jwplayer-api-client' 8 | spec.version = JWPlayer::API::Client::VERSION 9 | spec.authors = ['Raphael Daguenet'] 10 | spec.email = ['raphael.daguenet@gmail.com'] 11 | 12 | spec.summary = 'JWPlayer client to easily sign URLs' 13 | spec.description = 'This gem aims to easily sign JWPlayer Platform API URLs according to the documentation: https://developer.jwplayer.com/jw-platform/reference/v1/authentication.html' 14 | spec.homepage = 'https://github.com/raphi/jwplayer-api-client' 15 | spec.license = 'MIT' 16 | 17 | # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or 18 | # delete this section to allow pushing this gem to any host. 19 | if spec.respond_to?(:metadata) 20 | spec.metadata['allowed_push_host'] = 'https://rubygems.org' 21 | else 22 | raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' 23 | end 24 | 25 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 26 | spec.bindir = 'exe' 27 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 28 | spec.require_paths = ['lib'] 29 | 30 | spec.add_development_dependency 'bundler', '~> 1.11' 31 | spec.add_development_dependency 'rake', '~> 11.0' 32 | spec.add_development_dependency 'rspec', '~> 3.0' 33 | end 34 | -------------------------------------------------------------------------------- /lib/jwplayer/api/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'uri' 4 | require 'digest' 5 | require 'jwplayer/api/client/version' 6 | 7 | module JWPlayer 8 | module API 9 | class Client 10 | ALLOWED_KEYS = [:format, :key, :nonce, :timestamp] 11 | IGNORED_KEYS = [:host, :scheme, :secret, :signature, :version] 12 | ESCAPE_REGEX = /[^a-z0-9\-\.\_\~]/i # http://oauth.net/core/1.0/#encoding_parameters 13 | 14 | attr_reader :params, :options 15 | 16 | def initialize(args = {}) 17 | @options = { 18 | host: 'api.jwplatform.com', 19 | scheme: 'https', 20 | version: :v1, 21 | key: ENV['JWPLAYER_API_KEY'], 22 | secret: ENV['JWPLAYER_API_SECRET'], 23 | format: :json 24 | }.merge(args) 25 | 26 | [:key, :secret].each do |key| 27 | if options[key].nil? || options[key].empty? 28 | raise ArgumentError, "Missing :#{key} parameter or 'JWPLAYER_API_#{key.upcase}' ENV variable" 29 | end 30 | end 31 | end 32 | 33 | def signed_uri(path, params = {}) 34 | @params = params 35 | @options[:nonce] = rand.to_s[2..9] 36 | @options[:timestamp] = Time.now.to_i.to_s 37 | @uri = URI.join(URI::Generic.build(@options), [@options[:version], '/'].join, path) 38 | @uri.query = signed_attributes 39 | @uri.normalize! 40 | @uri 41 | end 42 | 43 | def signed_url(path, params = {}) 44 | signed_uri(path, params).to_s 45 | end 46 | 47 | private 48 | 49 | # 50 | # API signature generation 51 | # https://developer.jwplayer.com/jw-platform/reference/v1/authentication.html#api-signature-generation 52 | # 53 | 54 | def attributes 55 | matching_keys, extra_keys = options.keys.partition { |key| ALLOWED_KEYS.include?(key) } 56 | extra_keys -= IGNORED_KEYS 57 | 58 | raise ArgumentError, "#{self.class}: Unknown extra option keys\n [#{extra_keys.map(&:inspect).join(', ')}]" unless extra_keys.empty? 59 | 60 | matching_keys.map { |key| [:"api_#{key}", options[key]] } 61 | end 62 | 63 | def signed_attributes 64 | salted_params = salted_params(normalized_params) 65 | signature = signature(salted_params) 66 | 67 | to_query((params.to_a + attributes.to_a).push([:api_signature, signature])) 68 | end 69 | 70 | def normalized_params 71 | to_query(signature_params) 72 | end 73 | 74 | def signature_params 75 | sorted_params(params.to_a + attributes.to_a) 76 | end 77 | 78 | # 79 | # Steps 1 and 2 80 | # 1. All text parameters converted to UTF-8 encoding 81 | # 2. All text parameters URL-encoded 82 | # 83 | def escape(value) 84 | URI.escape(value.to_s, ESCAPE_REGEX) 85 | end 86 | 87 | # 88 | # Step 3 89 | # 3. Parameters are sorted based on their encoded names. Sort order is lexicographical byte value ordering 90 | # 91 | def sorted_params(params) 92 | params.sort 93 | end 94 | 95 | # 96 | # Step 4 97 | # 4. Parameters are concatenated together into a single query string 98 | # 99 | def to_query(params) 100 | params.map { |key, value| [key, escape(value)].join('=') }.join('&') 101 | end 102 | 103 | # 104 | # Step 5 105 | # The secret is added and SHA-1 digest is calculated 106 | # Secret is added to the end of the SBS 107 | # 108 | def salted_params(query_string) 109 | query_string + options[:secret].to_s 110 | end 111 | 112 | # 113 | # Step 6 114 | # The calculated SHA-1 HEX digest 115 | # 116 | def signature(token) 117 | Digest::SHA1.hexdigest(token) 118 | end 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/jwplayer/api/client/version.rb: -------------------------------------------------------------------------------- 1 | module JWPlayer 2 | module API 3 | class Client 4 | VERSION = '0.1.0' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/jwplayer/api/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'spec_helper' 3 | 4 | describe JWPlayer::API::Client do 5 | API_KEY = 'XOqEAfxj' 6 | API_SECRET = 'uA96CFtJa138E2T5GhKfngml' 7 | API_PATH = 'videos/list' 8 | 9 | let(:client) { JWPlayer::API::Client.new(key: API_KEY, secret: API_SECRET) } 10 | 11 | it 'has a version number' do 12 | expect(JWPlayer::API::Client::VERSION).not_to be nil 13 | end 14 | 15 | describe '::new' do 16 | context 'without any parameters' do 17 | it 'should raise an error' do 18 | expect { JWPlayer::API::Client.new }.to raise_error(ArgumentError) 19 | expect { JWPlayer::API::Client.new(key: API_KEY) }.to raise_error(ArgumentError) 20 | expect { JWPlayer::API::Client.new(secret: API_SECRET) }.to raise_error(ArgumentError) 21 | end 22 | 23 | context 'with JWPLAYER_API_* ENV variables' do 24 | it 'should not raise an error' do 25 | ENV['JWPLAYER_API_KEY'] = API_KEY 26 | ENV['JWPLAYER_API_SECRET'] = API_SECRET 27 | 28 | expect { JWPlayer::API::Client.new }.not_to raise_error 29 | end 30 | end 31 | end 32 | 33 | context 'with JWPlayer credentials' do 34 | it 'should have default values' do 35 | expect(client.options[:host]).to eql('api.jwplatform.com') 36 | expect(client.options[:scheme]).to eql('https') 37 | expect(client.options[:version]).to eql(:v1) 38 | expect(client.options[:key]).to eql(API_KEY) 39 | expect(client.options[:secret]).to eql(API_SECRET) 40 | expect(client.options[:format]).to eql(:json) 41 | end 42 | 43 | it 'should allow default values to be overrided' do 44 | opts = { 45 | key: API_KEY, 46 | secret: API_SECRET, 47 | host: 'www.myownhost.com', 48 | scheme: 'http', 49 | version: 'v42', 50 | format: :xml 51 | } 52 | 53 | client = JWPlayer::API::Client.new(opts) 54 | 55 | expect(client.options[:host]).to eql(opts[:host]) 56 | expect(client.options[:scheme]).to eql(opts[:scheme]) 57 | expect(client.options[:version]).to eql(opts[:version]) 58 | expect(client.options[:format]).to eql(opts[:format]) 59 | end 60 | end 61 | end 62 | 63 | # 64 | # Data examples, parameters and expected responses taken from: 65 | # https://developer.jwplayer.com/jw-platform/reference/v1/authentication.html 66 | # 67 | describe 'internal signing method' do 68 | let(:client) { JWPlayer::API::Client.new(key: API_KEY, secret: API_SECRET, format: :xml, scheme: 'http') } 69 | let(:params) { 70 | [ 71 | [:api_format, :xml], 72 | [:api_key, 'XOqEAfxj'], 73 | [:api_nonce, 80684843], 74 | [:api_timestamp, 1237387851], 75 | [:text, 'démo'] 76 | ] 77 | } 78 | 79 | describe '#attributes' do 80 | it 'should return a correct subset of attributes' do 81 | expect(client.send(:attributes).to_h.keys).to contain_exactly(:api_key, :api_format) 82 | end 83 | 84 | context 'with an extra unknown attribute' do 85 | let(:client) { JWPlayer::API::Client.new(key: API_KEY, secret: API_SECRET, extra: :forbidden) } 86 | 87 | it 'should raise an error if provided with unknown attributes' do 88 | expect { client.signed_uri(API_PATH) }.to raise_error(ArgumentError) 89 | end 90 | end 91 | end 92 | 93 | describe '#escape (steps 1 & 2)' do 94 | it 'should encode URL parameters' do 95 | expect(client.send(:escape, 'démo')).to eql('d%C3%A9mo') 96 | end 97 | end 98 | 99 | describe '#sorted_params (step 3)' do 100 | it 'should sort params based on their encoded names' do 101 | expect(client.send(:sorted_params, params.shuffle)).to eql([ 102 | [:api_format, :xml], 103 | [:api_key, 'XOqEAfxj'], 104 | [:api_nonce, 80684843], 105 | [:api_timestamp, 1237387851], 106 | [:text, 'démo'] 107 | ]) 108 | end 109 | end 110 | 111 | describe '#to_query (step 4)' do 112 | it 'should generate a correct query string' do 113 | expect(client.send(:to_query, params)).to eql('api_format=xml&api_key=XOqEAfxj&api_nonce=80684843&api_timestamp=1237387851&text=d%C3%A9mo') 114 | end 115 | end 116 | 117 | describe '#salted_params (step 5)' do 118 | it 'should append salt at the end of the query string' do 119 | query_string = 'api_format=xml&api_key=XOqEAfxj&api_nonce=80684843&api_timestamp=1237387851&text=d%C3%A9mo' 120 | 121 | expect(client.send(:salted_params, query_string)).to eql('api_format=xml&api_key=XOqEAfxj&api_nonce=80684843&api_timestamp=1237387851&text=d%C3%A9mouA96CFtJa138E2T5GhKfngml') 122 | end 123 | end 124 | 125 | describe '#signature (step 6)' do 126 | it 'should return a correct SHA-1 HEX digest' do 127 | token = 'api_format=xml&api_key=XOqEAfxj&api_nonce=80684843&api_timestamp=1237387851&text=d%C3%A9mouA96CFtJa138E2T5GhKfngml' 128 | 129 | expect(client.send(:signature, token)).to eql('fbdee51a45980f9876834dc5ee1ec5e93f67cb89') 130 | end 131 | end 132 | 133 | describe 'all steps' do 134 | it 'should generate a correctly signed URL' do 135 | allow(client).to receive(:options) { { key: API_KEY, secret: API_SECRET, format: :xml, nonce: '80684843', timestamp: '1237387851' } } 136 | 137 | signed_uri = client.signed_uri(API_PATH, text: 'démo') 138 | result_uri = URI.parse('http://api.jwplatform.com/v1/videos/list?text=d%C3%A9mo&api_nonce=80684843&api_timestamp=1237387851&api_format=xml&api_signature=fbdee51a45980f9876834dc5ee1ec5e93f67cb89&api_key=XOqEAfxj') 139 | 140 | expect(signed_uri.host).to eql(result_uri.host) 141 | expect(signed_uri.path).to eql(result_uri.path) 142 | expect(signed_uri.scheme).to eql(result_uri.scheme) 143 | expect(CGI.parse(signed_uri.query).sort).to eql(CGI.parse(result_uri.query).sort) 144 | end 145 | end 146 | end 147 | 148 | describe '#signed_uri' do 149 | let(:params) { CGI.parse(uri.query) } 150 | let(:uri) { client.signed_uri(API_PATH) } 151 | 152 | it 'should returns an ' do 153 | expect(uri).to be_a(URI::Generic) 154 | end 155 | 156 | it 'should generate random nonce each time called' do 157 | second_params = CGI.parse(client.signed_uri(API_PATH).query) 158 | 159 | expect(params['api_nonce']).to_not eql(second_params['api_nonce']) 160 | end 161 | 162 | describe '.href' do 163 | it 'should have a secure scheme' do 164 | expect(uri.scheme).to eql('https') 165 | end 166 | 167 | it 'should have jwplayer platform default host' do 168 | expect(uri.host).to eql('api.jwplatform.com') 169 | end 170 | end 171 | 172 | describe '.path' do 173 | it 'should have the API version' do 174 | expect(uri.path).to include('v1') 175 | end 176 | 177 | it 'should allow to override the relative path' do 178 | uri = client.signed_uri('/v42/this_is_non_sense') 179 | 180 | expect(uri.path).not_to include('v1') 181 | expect(uri.path).to include('v42') 182 | end 183 | end 184 | 185 | describe '.parameters' do 186 | it 'should have a JSON default format' do 187 | expect(params).to have_key('api_format') 188 | expect(params['api_format'].first).to eql('json') 189 | end 190 | 191 | it 'nonce must be 8 digits' do 192 | expect(/^\d{8}$/ === params['api_nonce'].first).to be_truthy 193 | end 194 | 195 | it 'has the required query parameters' do 196 | expect( 197 | [:api_key, :api_timestamp, :api_nonce, :api_signature].all? { |key| params.has_key?(key.to_s) } 198 | ).to be_truthy 199 | end 200 | 201 | it 'does not include forbidden parameters' do 202 | expect( 203 | [:api_secret, :secret, :api_host, :host, :api_scheme, :scheme].all? { |key| !params.has_key?(key.to_s) } 204 | ).to be_truthy 205 | end 206 | end 207 | 208 | context 'with extra query parameters' do 209 | let(:uri) { client.signed_uri(API_PATH, text: 'démo') } 210 | 211 | it 'should includes the given extra parameters' do 212 | expect(params).to have_key('text') 213 | end 214 | end 215 | end 216 | 217 | describe '#signed_url' do 218 | let(:url) { client.signed_url(API_PATH) } 219 | 220 | it 'should returns a ' do 221 | expect(url).to be_a(String) 222 | end 223 | 224 | it 'should returns a valid URL' do 225 | expect(url).to match(/\A#{URI::regexp(['http', 'https'])}\z/) 226 | end 227 | end 228 | end 229 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'jwplayer/api/client' 3 | --------------------------------------------------------------------------------