├── .bufferapprc.template ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── API_COVERAGE.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── TODO.md ├── bin └── buffer ├── buffer.gemspec ├── lib ├── buffer.rb └── buffer │ ├── client.rb │ ├── core.rb │ ├── datastructure.rb │ ├── encode.rb │ ├── error.rb │ ├── info.rb │ ├── link.rb │ ├── profile.rb │ ├── update.rb │ ├── user.rb │ └── version.rb └── spec ├── fixtures ├── destroy.txt ├── info.txt ├── interactions_by_update_id.txt ├── link.txt ├── profile_authenticated.txt ├── profile_schedules_by_id.txt ├── profiles_by_id.txt ├── update_by_id.txt ├── update_by_id_non_auth.txt ├── updates_by_profile_id.txt ├── updates_by_profile_id_pending.txt └── user_authenticated.txt ├── lib ├── buffer │ ├── encode_spec.rb │ ├── link_spec.rb │ ├── profile_spec.rb │ ├── schedule_spec.rb │ ├── update_spec.rb │ └── user_spec.rb ├── buffer_spec.rb └── core_spec.rb └── spec_helper.rb /.bufferapprc.template: -------------------------------------------------------------------------------- 1 | --- 2 | access_token: ASDFASDFASDF 3 | profile_index: 0 4 | version: 0.0.1 5 | 6 | # How to Get Started: 7 | # Create a Developer API Token here: http://bufferapp.com/developers/apps/create. 8 | # Fill in Stuff. Your answers don't matter much for the purpose of this rudimentary setup. 9 | # Submit that form and wait a short period (~2 min ) 10 | # Visit: http://bufferapp.com/developers/apps 11 | # Gather Access Token and place it after the word "access_token" 12 | # Copy this file to the root of your user's home folder: 13 | # Set "profile_index" to 0 if you only have one account to post to. Otherwise it's more complicated ;). Find me on Twitter and I can explain (@_ZPH). 14 | # - ~/.bufferapprc 15 | 16 | # Structure: 17 | # access_token: Access Token 18 | # profile_index: Buffer Account number, ie posting to first account in list, use 0 (ie zero) 19 | # 20 | #TODO: improve instructions 21 | # remove need for user to create their own App on bufferapp.com 22 | # Future versions will integrate with Buffer-OAuth system. 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | notes.txt 19 | .DS_STORE 20 | spec/fixtures/updates_by_profile_id_sent.txt 21 | utility.rb 22 | tags 23 | vendor 24 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Encoding: 2 | Enabled: true 3 | 4 | LineLength: 5 | Enabled: true 6 | Max: 79 7 | 8 | StringLiterals: 9 | Enabled: false 10 | 11 | AllCops: 12 | # Includes: 13 | Excludes: 14 | - spec/** 15 | - bin/** 16 | - utility.rb 17 | - Rakefile 18 | - tmp/* 19 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.1.5 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0.0 4 | env: 5 | - TRAVIS_CI=true 6 | -------------------------------------------------------------------------------- /API_COVERAGE.md: -------------------------------------------------------------------------------- 1 | ## Fully Implemented API Requests 2 | GET https://api.bufferapp.com/1/user.json 3 | GET https://api.bufferapp.com/1/profiles.json 4 | GET https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010.json 5 | GET https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010/schedules.json 6 | GET https://api.bufferapp.com/1/updates/4eb8565e0acb04bb82000004.json 7 | POST https://api.bufferapp.com/1/updates/4ecda256512f7ee521000001/share.json 8 | POST https://api.bufferapp.com/1/updates/4ecda256512f7ee521000004/destroy.json 9 | POST https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010/schedules/update.json 10 | GET https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010/updates/pending.json 11 | GET https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010/updates/sent.json 12 | GET https://api.bufferapp.com/1/updates/4ecda476542f7ee521000006/interactions.json 13 | POST https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010/updates/shuffle.json 14 | POST https://api.bufferapp.com/1/updates/create.json 15 | POST https://api.bufferapp.com/1/updates/4ecda256512f7ee521000004/update.json 16 | POST https://api.bufferapp.com/1/profiles/4eb854340acb04e870000010/updates/reorder.json 17 | 18 | ## Untested Optional Params 19 | 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'coveralls', require: false 4 | gemspec 5 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :rspec do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { "spec" } 8 | 9 | end 10 | 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 ZPH 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # buffer 2 | 3 | buffer is a Buffer API Wrapper written in Ruby. It provides more thorough API coverage than the existing gem. 4 | 5 | ## Installation 6 | 7 | [![Coverage Status](https://coveralls.io/repos/zph/buff/badge.png?branch=master)](https://coveralls.io/r/zph/buff?branch=master) [![Build Status](https://travis-ci.org/zph/buff.png?branch=master)](https://travis-ci.org/zph/buff) [![Code Climate](https://codeclimate.com/github/zph/buff.png)](https://codeclimate.com/github/zph/buff) 8 | 9 | For now please `git clone git@github.com:bufferapp/buffer-ruby.git` the repo 10 | 11 | Or 12 | 13 | Add this line to your application's Gemfile to include HEAD code: 14 | 15 | `gem 'buffer', :github => 'bufferapp/buffer-ruby'` 16 | 17 | And then execute: 18 | 19 | `$ bundle` 20 | 21 | Or install RubyGems version, which will receive more attention to stability: 22 | 23 | `$ gem install buffer` 24 | 25 | ## Usage 26 | 27 | * All methods are tested with Rspec and WebMock. Most methods do not have integration tests that reach out to the live Buffer API servers. Proceed with caution until buffer reaches v0.1.0 and submit issues on Github Issues tab. 28 | * Authentication is not included in this gem (Try OAuth-buffer2) or use the single API key given when registering your own Buffer Dev credentials. 29 | * Commandline bin is provided to enable posting of updates: 30 | `buffer Super witty stuff that fits in 140 chars` 31 | Will post to your first account when setup following instructions below. 32 | _A more convenient setup is planned in future releases._ 33 | * For convenience load credentials into environment as ENV variables: 34 | 35 | ``` 36 | export BUFFER_ACCESS_TOKEN="1/jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj" # (BufferApp OAuth Access Token) 37 | export BUFFER_PROFILE_ID="0" # (default of 0) 38 | ``` 39 | 40 | If you wish to post to multiple ids from the commandline, BUFFER_PROFILE_ID accepts a 41 | comma delimited array of integers, ie `BUFFER_PROFILE_ID="0,1"`. This will post to both 42 | 0 and 1 index in your profiles list. 43 | 44 | ## Access Token Instructions 45 | 46 | #### How to Get Started: 47 | 48 | * Create a Developer API Token here: http://bufferapp.com/developers/apps/create. 49 | * Fill in Stuff. Your answers don't matter much for the purpose of this rudimentary setup. 50 | * Submit that form and wait a short period (~2 min ) 51 | * Visit: http://bufferapp.com/developers/apps 52 | * Gather Access Token and place it after the word "access_token" 53 | * Set BUFFER_PROFILE_ID="0" if you only have one account to post to. Otherwise it's more complicated ;). 54 | 55 | #### Example 56 | 57 | The example below will use your Buffer account and schedule an update to be posted on your connected profiles with the specified IDs. 58 | 59 | ``` 60 | client = Buffer::Client.new(ACCESS_TOKEN) 61 | client.create_update( 62 | body: { 63 | text: 64 | "Today's artist spotlight is on #{artist_name}. 65 | Check out the track, #{track_title}.", 66 | profile_ids: [ 67 | '...', 68 | '...', 69 | ] 70 | }, 71 | ) 72 | ``` 73 | 74 | ## TODO: 75 | 76 | * Improve instructions 77 | 78 | #### Future versions will integrate with Buffer-OAuth system. 79 | * Integrate Launchy for the purpose of launching browser window. 80 | * Possible to model behavior on [ t.gem ](https://github.com/sferik/t/blob/master/lib/t/cli.rb#L56-L113) 81 | 82 | #### Raise error if message is beyond the character limit. 83 | * Accomplish this via [ Twitter Text library ](https://github.com/twitter/twitter-text-rb) 84 | * Refactor to simplify use of default params 85 | 86 | ## API Coverage 87 | 88 | #### Implemented 89 | 90 | * User 91 | * Profiles (:get, :post) 92 | * Updates (:get, :post) 93 | * Links 94 | * Info 95 | * Error Codes 96 | 97 | Further Details [API Coverage](API_COVERAGE.md) 98 | 99 | #### Not Implemented 100 | 101 | * Caching 102 | 103 | ## Supported Ruby Implementations 104 | - MRI 2.0.0 105 | - Others likely work but are not included in CI Server 106 | 107 | ## Contributing 108 | 109 | 1. Fork it 110 | 2. Create your feature branch (`git checkout -b my-new-feature`) 111 | 3. Commit your changes (`git commit -am 'Add some feature'`) 112 | 4. Push to the branch (`git push origin my-new-feature`) 113 | 5. Create new Pull Request 114 | 115 | Issues, refactoring, and feedback are all welcome. 116 | 117 | Also, this project is newcomer friendly!! We'd love to be your first Open Source Software contribution and would be happy to assist in that process. 118 | 119 | Crafted with care by Zander. Reach out and say hi at [@_ZPH](http://twitter.com/_ZPH) or [civet.ws](http://www.civet.ws) 120 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rake' 3 | require 'rspec/core/rake_task' 4 | 5 | desc 'Default: run specs.' 6 | task :default => :spec 7 | 8 | desc "Run specs" 9 | RSpec::Core::RakeTask.new do |t| 10 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default. 11 | # Put spec opts in a file named .rspec in root 12 | end 13 | 14 | desc "Generate code coverage" 15 | RSpec::Core::RakeTask.new(:coverage) do |t| 16 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default. 17 | t.rcov = true 18 | t.rcov_opts = %w[--exclude spec] 19 | end 20 | 21 | task :default => :spec 22 | 23 | task :curl_dump, [ :url ] do |t, args| 24 | access_token = `cat ~/.bufferapprc | head -3 | tail -1`.chomp 25 | sh "curl -is #{args[:url]}?access_token=#{access_token}" 26 | end 27 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Improve Interface for #create_update to not need :body in args 2 | Add error if message is obviously above 140 char 3 | - Extra feature to shorten web links to length used by t.co 4 | -------------------------------------------------------------------------------- /bin/buffer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'buffer' 4 | require 'logger' 5 | 6 | log_level = ENV['BUFFER_DEBUG'] ? Logger::INFO : Logger::ERROR 7 | 8 | LOG = Logger.new(STDOUT) { |l| l.log_level = log_level } 9 | 10 | def gather_message(argv) 11 | args = argv.dup 12 | case args.count 13 | when 0 then abort("Please supply a message for Buffer Post.") 14 | when 1 then args.first 15 | else 16 | args.join(" ") 17 | end 18 | end 19 | 20 | def parse_profile_ids(env = ENV) 21 | profile = ENV.fetch('BUFFER_PROFILE_ID', "0") 22 | profiles = if profile.include?(",") 23 | profile.split(",") 24 | else 25 | Array(profile) 26 | end 27 | profiles.map { |i| Integer(i) } 28 | end 29 | 30 | def main 31 | token = Env.BUFFER_ACCESS_TOKEN 32 | message = gather_message(ARGV) 33 | client = Buffer::Client.new(token) 34 | all_profiles = client.profiles 35 | desired_profiles = parse_profile_ids.map do |i| 36 | begin 37 | all_profiles[i]["_id"] 38 | rescue 39 | abort("ERROR: Unable to find all the requested profiles. Profile Index #{i} was the issue.") 40 | end 41 | end 42 | post_args = {text: message, profile_ids: desired_profiles} 43 | LOG.info('#main.post_args') { post_args } 44 | unless ENV['BUFFER_DEBUG'] 45 | client.create_update(body: post_args) 46 | puts "Posted message." 47 | end 48 | end 49 | 50 | main 51 | -------------------------------------------------------------------------------- /buffer.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'buffer/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "buffer" 8 | gem.version = Buffer::VERSION 9 | gem.authors = ["ZPH"] 10 | gem.email = ["Zander@civet.ws"] 11 | gem.description = %q{Buffer is an API Wrapper Gem for Bufferapp.com's API} 12 | gem.summary = %q{Buffer is an API Wrapper Gem for Bufferapp.com's API} 13 | gem.homepage = "http://github.com/bufferapp/buffer-ruby" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.add_development_dependency 'rspec' 21 | gem.add_development_dependency 'webmock' 22 | gem.add_development_dependency 'guard-rspec' 23 | gem.add_development_dependency 'guard-bundler' 24 | gem.add_development_dependency 'rb-fsevent' 25 | gem.add_development_dependency 'growl' 26 | gem.add_development_dependency 'pry-uber' 27 | 28 | gem.add_runtime_dependency 'multi_json' 29 | gem.add_runtime_dependency 'yajl-ruby' 30 | gem.add_runtime_dependency 'faraday_middleware' 31 | gem.add_runtime_dependency 'faraday' 32 | gem.add_runtime_dependency 'hashie' 33 | gem.add_runtime_dependency 'rake' 34 | gem.add_runtime_dependency 'addressable' 35 | gem.add_runtime_dependency 'environs' 36 | end 37 | -------------------------------------------------------------------------------- /lib/buffer.rb: -------------------------------------------------------------------------------- 1 | require 'faraday' 2 | require 'faraday_middleware' 3 | require 'json' 4 | require 'hashie/mash' 5 | require 'addressable/uri' 6 | require 'environs' 7 | 8 | require 'buffer/version' 9 | require 'buffer/core' 10 | require 'buffer/user' 11 | require 'buffer/profile' 12 | require 'buffer/update' 13 | require 'buffer/link' 14 | require 'buffer/error' 15 | require 'buffer/encode' 16 | require 'buffer/datastructure' 17 | require 'buffer/info' 18 | require 'buffer/client' 19 | 20 | module Buffer 21 | end 22 | -------------------------------------------------------------------------------- /lib/buffer/client.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Client 3 | include Core 4 | include User 5 | include Profile 6 | include Update 7 | include Link 8 | include Info 9 | 10 | attr_accessor :access_token 11 | 12 | URL = 'https://api.bufferapp.com/1/'.freeze 13 | 14 | def initialize(access_token) 15 | @access_token = access_token 16 | end 17 | 18 | def connection 19 | @connection ||= Faraday.new(url: URL) do |faraday| 20 | faraday.request :url_encoded 21 | faraday.adapter Faraday.default_adapter 22 | end 23 | end 24 | 25 | def auth_query 26 | { access_token: @access_token } 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/buffer/core.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module Buffer 4 | class Client 5 | module Core 6 | API_VERSION = "1" 7 | 8 | private 9 | 10 | def get(path, options = {}) 11 | options.merge!(auth_query) 12 | response = connection.get do |req| 13 | req.url path.remove_leading_slash 14 | req.params = options 15 | end 16 | 17 | interpret_response(response) 18 | end 19 | 20 | def post(path, options = {}) 21 | params = merge_auth_token_and_query(options) 22 | params.merge!(options) 23 | response = connection.post do |req| 24 | req.url path.remove_leading_slash 25 | req.headers['Content-Type'] = "application/x-www-form-urlencoded" 26 | req.body = options[:body] 27 | req.params = params 28 | end 29 | 30 | Hashie::Mash.new(JSON.parse response.body) 31 | end 32 | 33 | def merge_auth_token_and_query(options) 34 | if options[:query] 35 | auth_query.merge options[:query] 36 | else 37 | auth_query 38 | end 39 | end 40 | 41 | def interpret_response(response) 42 | if response.status == 200 43 | JSON.parse response.body 44 | else 45 | handle_response_code(response) 46 | end 47 | end 48 | 49 | def handle_response_code(response) 50 | error = Hashie::Mash.new(JSON.parse(response.body)) 51 | raise Buffer::Error::APIError, 52 | "Buffer API Error Code: #{error.code} " + 53 | "HTTP Code: #{response.status}. " + 54 | "Description: #{error.error}" 55 | end 56 | end 57 | end 58 | end 59 | 60 | class String 61 | def remove_leading_slash 62 | gsub(/^\//, '') 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/buffer/datastructure.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class UserInfo < Hashie::Mash; end 3 | class Profile < Hashie::Mash; end 4 | class Response < Hashie::Mash; end 5 | class Update < Hashie::Mash; end 6 | class Updates < Hashie::Mash; end 7 | class Interaction < Hashie::Mash; end 8 | class Interactions < Hashie::Mash; end 9 | class Link < Hashie::Mash; end 10 | class Info < Hashie::Mash; end 11 | 12 | class Schedule < Hashie::Mash; end 13 | Schedules = Class.new(Array) do 14 | def dump 15 | { schedules: self }.to_json 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/buffer/encode.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Encode 3 | class << self 4 | def encode(arg) 5 | raise_error_for_incorrect_input(arg) 6 | arg = arg[:schedules] if arg.respond_to?(:keys) 7 | arg.map.with_index do |item, index| 8 | process_schedule(item, index) 9 | end.join("&") 10 | end 11 | 12 | private 13 | 14 | def raise_error_for_incorrect_input(arg) 15 | unless arg.kind_of?(Hash) || arg.kind_of?(Array) 16 | raise ArgumentError, "Input must be/inherit from Hash or Array" 17 | end 18 | end 19 | 20 | def process_schedule(item, index) 21 | pairs_for(item).map do |key, value| 22 | "schedules[#{index}][#{key}][]=#{value}" 23 | end.join("&") 24 | end 25 | 26 | def pairs_for(item) 27 | uri = Addressable::URI.new 28 | uri.query_values = item 29 | uri.query.split("&").map {|p| p.split("=")} 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/buffer/error.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | module Error 3 | ConfigFileMissing = Class.new(StandardError) 4 | InvalidIdLength = Class.new(ArgumentError) 5 | InvalidIdContent = Class.new(ArgumentError) 6 | MissingStatus = Class.new(ArgumentError) 7 | APIError = Class.new(StandardError) 8 | UnauthorizedRequest = Class.new(StandardError) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/buffer/info.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Client 3 | module Info 4 | def info 5 | response = get("/info/configuration.json") 6 | Buffer::Info.new(response) 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/buffer/link.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Client 3 | module Link 4 | def link(options) 5 | response = get("/links/shares.json", options) 6 | Buffer::Link.new(response) 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/buffer/profile.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Client 3 | module Profile 4 | def profiles 5 | response = get("/profiles.json") 6 | response.map { |profile| Buffer::Profile.new(profile) } 7 | end 8 | 9 | def profile_by_id(id) 10 | response = get("/profiles/#{id}.json") 11 | Buffer::Profile.new(response) 12 | end 13 | 14 | def schedules_by_profile_id(id) 15 | response = get("/profiles/#{id}/schedules.json") 16 | response.map { |a_response| Buffer::Schedule.new(a_response) } 17 | end 18 | 19 | def set_schedules(id, options) 20 | schedules = Buffer::Encode.encode( 21 | options.fetch(:schedules) { raise ArgumentError }) 22 | post("/profiles/#{id}/schedules/update.json", 23 | body: { schedules: schedules }) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/buffer/update.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Client 3 | module Update 4 | def update_by_id(id, options = {}) 5 | check_id(id) 6 | response = get("/updates/#{id}.json") 7 | Buffer::Update.new(response) 8 | end 9 | 10 | def updates_by_profile_id(id, options = {}) 11 | status = options.fetch(:status) do 12 | raise Buffer::Error::MissingStatus, "Include :pending or :sent in args" 13 | end 14 | options.delete(:status) 15 | response = get("/profiles/#{id}/updates/#{status.to_s}.json", options) 16 | updates = response['updates'].map { |r| Buffer::Update.new(r) } 17 | Buffer::Updates.new ( 18 | { total: response['total'], 19 | updates: updates } 20 | ) 21 | end 22 | 23 | def interactions_by_update_id(id, options = {}) 24 | check_id(id) 25 | response = get("/updates/#{id}/interactions.json", options) 26 | interactions = response['interactions'].map do |r| 27 | Buffer::Interaction.new(r) 28 | end 29 | Buffer::Interactions.new( 30 | { total: response['total'], interactions: interactions } 31 | ) 32 | end 33 | 34 | def reorder_updates(profile_id, options = {}) 35 | options.fetch(:order) { raise ArgumentError } 36 | post("/profiles/#{profile_id}/updates/reorder.json", body: options) 37 | end 38 | 39 | def shuffle_updates(profile_id, options = {}) 40 | post("/profiles/#{profile_id}/updates/shuffle.json", 41 | body: options) 42 | end 43 | 44 | def create_update(options = {}) 45 | options[:body].fetch(:text) do 46 | raise ArgumentError, "Must include text for update" 47 | end 48 | options[:body].fetch(:profile_ids) do 49 | raise ArgumentError, "Must include array of profile_ids" 50 | end 51 | post("/updates/create.json", options) 52 | end 53 | 54 | def modify_update_text(update_id, options = {}) 55 | options[:body].fetch(:text) do 56 | raise ArgumentError, "Must include updated text" 57 | end 58 | post("/updates/#{update_id}/update.json", options) 59 | end 60 | 61 | def share_update(update_id) 62 | post("/updates/#{update_id}/share.json") 63 | end 64 | 65 | def destroy_update(update_id) 66 | post("/updates/#{update_id}/destroy.json") 67 | end 68 | 69 | def check_id(id) 70 | raise Buffer::Error::InvalidIdLength unless id.length == 24 71 | raise Buffer::Error::InvalidIdContent unless id[/^[a-f0-9]+$/i] 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/buffer/user.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | class Client 3 | module User 4 | def user_info(options = {}) 5 | Buffer::UserInfo.new(get("/user.json")) 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/buffer/version.rb: -------------------------------------------------------------------------------- 1 | module Buffer 2 | VERSION = "0.1.3" 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/destroy.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Wed, 15 May 2013 01:43:34 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 828 8 | Connection: keep-alive 9 | 10 | {"success":true,"message":"Update deleted successfully"} 11 | -------------------------------------------------------------------------------- /spec/fixtures/info.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Thu, 16 May 2013 22:05:18 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 2820 8 | Connection: keep-alive 9 | 10 | {"services":{"twitter":{"types":{"profile":{"name":"Twitter","character_limit":140,"schedule_limit":288,"icons":{"16":"http:\/\/static.bufferapp.com\/images\/services\/twitter-16x16.png","32":"http:\/\/static.bufferapp.com\/images\/services\/twitter-32x32.png","64":"http:\/\/static.bufferapp.com\/images\/services\/twitter-64x64.png"},"supported_interactions":["favorites","favorite","mentions","mention","retweets","retweet"]}},"urls":{"user":"https:\/\/twitter.com\/","hashtag":"https:\/\/twitter.com\/#!\/search?q=%23","cashtag":"https:\/\/twitter.com\/#!\/search?q=%24"}},"facebook":{"types":{"profile":{"name":"Facebook","character_limit":5000,"schedule_limit":5,"icons":{"16":"http:\/\/static.bufferapp.com\/images\/services\/facebook-16x16.png","32":"http:\/\/static.bufferapp.com\/images\/services\/facebook-32x32.png","64":"http:\/\/static.bufferapp.com\/images\/services\/facebook-64x64.png"},"supported_interactions":["likes","like","comments","comment"]},"page":{"name":"Facebook Page","character_limit":5000,"schedule_limit":5,"icons":{"16":"http:\/\/static.bufferapp.com\/images\/services\/facebook-16x16.png","32":"http:\/\/static.bufferapp.com\/images\/services\/facebook-32x32.png","64":"http:\/\/static.bufferapp.com\/images\/services\/facebook-64x64.png"},"supported_interactions":["likes","like","comments","comment"]}},"urls":{"user":"https:\/\/www.facebook.com\/"}},"linkedin":{"types":{"profile":{"name":"LinkedIn","character_limit":700,"schedule_limit":25,"icons":{"16":"http:\/\/static.bufferapp.com\/images\/services\/linkedin-16x16.png","32":"http:\/\/static.bufferapp.com\/images\/services\/linkedin-32x32.png","64":"http:\/\/static.bufferapp.com\/images\/services\/linkedin-64x64.png"},"supported_interactions":["comments","comment","likes","like"]},"group":{"name":"LinkedIn Group","character_limit":200,"schedule_limit":100,"icons":{"16":"http:\/\/static.bufferapp.com\/images\/services\/linkedin-16x16.png","32":"http:\/\/static.bufferapp.com\/images\/services\/linkedin-32x32.png","64":"http:\/\/static.bufferapp.com\/images\/services\/linkedin-64x64.png"},"supported_interactions":["comments","comment","likes","like"]}},"urls":{"user":"http:\/\/www.linkedin.com\/search\/fpsearch?type=people&keywords="}},"appdotnet":{"types":{"profile":{"name":"App.net","character_limit":256,"schedule_limit":288,"icons":{"16":"http:\/\/static.bufferapp.com\/images\/services\/appdotnet-alpha-16x16.png","32":"http:\/\/static.bufferapp.com\/images\/services\/appdotnet-alpha2-32x32.png","64":"http:\/\/static.bufferapp.com\/images\/services\/appdotnet-alpha-64x64.png"},"supported_interactions":[]}},"urls":{"user":"https:\/\/alpha.app.net\/","hashtag":"https:\/\/alpha.app.net\/hashtags\/"}}},"media":{"picture_size_min":0,"picture_size_max":5242880,"picture_filetypes":["jpeg","jpg","gif","png"]}} -------------------------------------------------------------------------------- /spec/fixtures/interactions_by_update_id.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Fri, 17 May 2013 04:21:13 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 29 8 | Connection: keep-alive 9 | 10 | {"total":0,"interactions":[]} -------------------------------------------------------------------------------- /spec/fixtures/link.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Wed, 15 May 2013 01:43:34 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 828 8 | Connection: keep-alive 9 | 10 | { 11 | "shares":47348 12 | } 13 | -------------------------------------------------------------------------------- /spec/fixtures/profile_authenticated.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Tue, 14 May 2013 05:02:18 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | X-Cache: Awwww Yea 7 | x-frame-options: SAMEORIGIN 8 | Content-Length: 845 9 | Connection: keep-alive 10 | 11 | [{"_id":"5160746d13f04a5e3a00000f","avatar":"http:\/\/a0.twimg.com\/profile_images\/3604112130.jpeg","avatar_https":"https:\/\/twimg0-a.akamaihd.net\/profile_images\/3604112130.jpeg","counts":{"pending":1,"sent":40},"created_at":1365275757,"default":true,"disconnected":null,"formatted_service":"Twitter","formatted_username":"@example","id":"5160746d13d04a5e3a00000f","quick_signin":true,"schedules":[{"days":["mon","tue","wed","thu","fri","sat","sun"],"times":["06:13","09:10","11:58","12:17","13:49","18:07","21:04","22:13"]}],"service":"twitter","service_id":"737649619","service_type":"profile","service_username":"example","statistics":{"followers":179},"timezone":"America\/New_York","user_id":"5160746d13d04a5e3a00000d","utm_tracking":"enabled","verb":"tweet"}] 12 | -------------------------------------------------------------------------------- /spec/fixtures/profile_schedules_by_id.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Wed, 15 May 2013 01:53:50 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 128 8 | Connection: keep-alive 9 | 10 | [{"days":["mon","tue","wed","thu","fri","sat","sun"],"times":["06:13","09:10","11:58","12:17","13:49","18:07","21:04","22:13"]}] -------------------------------------------------------------------------------- /spec/fixtures/profiles_by_id.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Wed, 15 May 2013 01:43:34 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 828 8 | Connection: keep-alive 9 | 10 | {"_id":"5160746d13d04a5e3a00000f","avatar":"http:\/\/a0.twimg.com\/profile_images\/3604112130.jpeg","avatar_https":"https:\/\/twimg0-a.akamaihd.net\/profile_images\/3604112130.jpeg","counts":{"pending":1,"sent":46},"created_at":1365275757,"disconnected":null,"formatted_service":"Twitter","formatted_username":"@example","id":"5160746d13d04a5e3a00000f","quick_signin":true,"schedules":[{"days":["mon","tue","wed","thu","fri","sat","sun"],"times":["06:13","09:10","11:58","12:17","13:49","18:07","21:04","22:13"]}],"service":"twitter","service_id":"737649619","service_type":"profile","service_username":"example","statistics":{"followers":179},"timezone":"America\/New_York","user_id":"5160746d13d04a5e3a00000d","utm_tracking":"enabled","verb":"tweet"} 11 | -------------------------------------------------------------------------------- /spec/fixtures/update_by_id.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Wed, 15 May 2013 01:43:34 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 828 8 | Connection: keep-alive 9 | 10 | {"id":"4eb8565e0acb04bb82000004","created_at":1320703582,"day":"Monday 7th November","due_at":1320742680,"due_time":"10:09 pm","profile_id":"4eb854340acb04e870000010","profile_service":"twitter","sent_at":1320744001,"service_update_id":"133667319959392256","statistics":{"reach":2460,"clicks":56,"retweets":20,"favorites":1,"mentions":1},"status":"sent","text":"This is just the beginning, the very beginning, of the transfor...","text_formatted":"This is just the beginning, the very beginning, of th...","user_id":"4eb9276e0acb04bb81000067","via":"chrome"} 11 | -------------------------------------------------------------------------------- /spec/fixtures/update_by_id_non_auth.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 401 Authorization Required 2 | Content-Type: text/html; charset=UTF-8 3 | Date: Fri, 17 May 2013 02:46:37 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | WWW-Authenticate: OAuth realm='Service', error='invalid_request', error_description='The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.' 7 | Content-Length: 0 8 | Connection: keep-alive 9 | 10 | -------------------------------------------------------------------------------- /spec/fixtures/updates_by_profile_id.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Wed, 15 May 2013 01:43:34 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 828 8 | Connection: keep-alive 9 | 10 | {"total":8,"updates":[{"id":"4ec93ae4512f7e6307000002","created_at":1320703582,"day":"Monday 7th November","due_at":1320543480,"due_time":"07:01 pm","profile_id":"4eb854340acb04e870000010","profile_service":"twitter","status":"buffer","text":"This is me in an alternate life where i can breakdance j.mp/w...","text_formatted":"This is me in an alternate life where i can breakda...","user_id":"4eb9276e0acb04bb81000067","via":"firefox"},] } 11 | -------------------------------------------------------------------------------- /spec/fixtures/updates_by_profile_id_pending.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Awesome 2 | Content-Type: application/json;charset=UTF-8 3 | Date: Thu, 16 May 2013 04:48:14 GMT 4 | Server: Apache 5 | Vary: Accept-Encoding,User-Agent 6 | x-frame-options: SAMEORIGIN 7 | Content-Length: 737 8 | Connection: keep-alive 9 | 10 | {"total":1,"updates":[{"_id":"519464f2872cad3734000028","created_at":1368679666,"day":"Today","due_at":1368699180,"due_time":"6:13 am","id":"519464f2872cad3734000028","profile_id":"5160746d13d04a5e3a00000f","profile_service":"twitter","shared_now":false,"status":"buffer","text":"Exciting to have even 1 person fork my fork of a repo :) jaysonlane\/buffer-ruby \u00b7 GitHub http:\/\/bit.ly\/15OS4ap","text_formatted":"Exciting to have even 1 person fork my fork of a repo :) jaysonlane\/buffer-ruby \u00b7 GitHub http:\/\/bit.ly\/15OS4ap<\/a>","type":"link","updated_at":1368679666,"user_id":"5160746d54f04a5e3a00000d","via":"bookmarklet"}]} 11 | -------------------------------------------------------------------------------- /spec/fixtures/user_authenticated.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx/0.7.65 3 | Date: Thu, 03 Mar 2011 19:25:34 GMT 4 | Content-Type: application/json 5 | Connection: keep-alive 6 | Keep-Alive: timeout=20 7 | Content-Length: 380 8 | 9 | { 10 | "_id":"4f0c0a06512f7ef214000000", 11 | "activity_at":1343654640, 12 | "created_at":1326189062, 13 | "id":"4f0c0a06512f7ef214000000", 14 | "plan":"free", 15 | "referral_link":"http:\/\/bufferapp.com\/r\/abcde", 16 | "referral_token":"abcde", 17 | "secret_email":"supersecret@to.bufferapp.com", 18 | "timezone":"Asia\/Tel_Aviv" 19 | } 20 | -------------------------------------------------------------------------------- /spec/lib/buffer/encode_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Buffer::Encode do 4 | 5 | context "successful code" do 6 | 7 | let(:schedule_first) { { :days => ["mon", "tue", "wed", "thu"], :times => ["12:00", "13:00"]} } 8 | let(:schedule_second) { { :days => ["sun", "sat"], :times => ["09:00", "24:00"]} } 9 | let(:schedules_hash) { { schedules: [schedule_first, schedule_second] } } 10 | let(:short_schedule) { { days: ["mon", "tue", "wed"], times: ["12:00", "17:00", "18:00"]} } 11 | let(:short_schedule_encoded) { "[days][]=mon&[days][]=tue&[times][]=12%3A00&[times][]=13%3A00" } 12 | let(:schedules_encoded) { "schedules[0][days][]=mon&schedules[0][days][]=tue&schedules[0][days][]=wed&schedules[0][times][]=12:00&schedules[0][times][]=17:00&schedules[0][times][]=18:00" } 13 | let(:very_short_schedule) { { :days => ["sun", "sat"], :times => ["09:00", "24:00"]} } 14 | 15 | 16 | describe "#encode" 17 | it "converts to match Buffer API specs encoding" do 18 | Buffer::Encode.encode([short_schedule]). 19 | should eq(schedules_encoded.gsub(/:/, '%3A')) 20 | end 21 | 22 | it "processes an input array of schedules" do 23 | Buffer::Encode.encode([very_short_schedule, very_short_schedule]). 24 | should eq("schedules[0][days][]=sun&schedules[0][days][]=sat&schedules[0][times][]=09%3A00&schedules[0][times][]=24%3A00&schedules[1][days][]=sun&schedules[1][days][]=sat&schedules[1][times][]=09%3A00&schedules[1][times][]=24%3A00") 25 | end 26 | 27 | it "includes index in conversion when multiple schedules present" do 28 | Buffer::Encode.encode([very_short_schedule, very_short_schedule, very_short_schedule]). 29 | should eq("schedules[0][days][]=sun&schedules[0][days][]=sat&schedules[0][times][]=09%3A00&schedules[0][times][]=24%3A00&schedules[1][days][]=sun&schedules[1][days][]=sat&schedules[1][times][]=09%3A00&schedules[1][times][]=24%3A00&schedules[2][days][]=sun&schedules[2][days][]=sat&schedules[2][times][]=09%3A00&schedules[2][times][]=24%3A00") 30 | end 31 | 32 | it "processes an input hash" do 33 | Buffer::Encode.encode({ schedules: [very_short_schedule, very_short_schedule, very_short_schedule] }). 34 | should eq("schedules[0][days][]=sun&schedules[0][days][]=sat&schedules[0][times][]=09%3A00&schedules[0][times][]=24%3A00&schedules[1][days][]=sun&schedules[1][days][]=sat&schedules[1][times][]=09%3A00&schedules[1][times][]=24%3A00&schedules[2][days][]=sun&schedules[2][days][]=sat&schedules[2][times][]=09%3A00&schedules[2][times][]=24%3A00") 35 | end 36 | end 37 | 38 | describe "#encode_schedule_primary" do 39 | end 40 | 41 | 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/buffer/link_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Buffer::Client do 4 | describe "#link" do 5 | let(:client) { Buffer::Client.new("some_token") } 6 | let(:url) { %q{http://bufferapp.com} } 7 | 8 | before do 9 | stub_request(:get, "#{ base_path }/links/shares.json?#{ access_token_param }&url=http://bufferapp.com"). 10 | to_return(fixture('link.txt')) 11 | end 12 | 13 | it "connects to the correct endpoint" do 14 | client.link({url: url}) 15 | end 16 | 17 | it "parses the shares of a link" do 18 | client.link({url: url}).shares.should eq(47348) 19 | end 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/lib/buffer/profile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Buffer::Client::Profile do 4 | let(:id) { "5160746d54f04a5e3a00000f" } 5 | 6 | subject do 7 | Buffer::Client.new("some_token") 8 | end 9 | 10 | describe "#profiles" do 11 | let(:rash) { Buffer::Client.new("some_token").profiles } 12 | 13 | before(:each) do 14 | url = "#{ base_path }/profiles.json" 15 | stub_with_to_return(:get, url, 'profile_authenticated.txt') 16 | end 17 | 18 | it "makes the correct url request" do 19 | subject.profiles 20 | end 21 | 22 | it "returns a Rash collection object" do 23 | rash[0].class.should eq(Buffer::Profile) 24 | end 25 | 26 | it "provides an accessor for plan" do 27 | rash[0].service.should eq("twitter") 28 | end 29 | end 30 | 31 | describe "#profile_by_id" do 32 | let(:id) { "5160746d54f04a5e3a00000f" } 33 | before(:each) do 34 | url = "#{base_path}/profiles/#{id}.json" 35 | fixture_name = "profiles_by_id.txt" 36 | stub_with_to_return(:get, url, fixture_name) 37 | end 38 | 39 | let(:rash) { Buffer::Client.new("some_token").profile_by_id(id) } 40 | 41 | it "returns a rash collection" do 42 | rash.class.should eq(Buffer::Profile) 43 | end 44 | 45 | it "accesses formatted service" do 46 | rash.formatted_service.should eq("Twitter") 47 | end 48 | end 49 | 50 | describe "#schedules_by_profile_id" do 51 | before(:each) do 52 | url = "#{base_path}/profiles/#{id}/schedules.json" 53 | fixture_name = 'profile_schedules_by_id.txt' 54 | stub_with_to_return(:get, url, fixture_name) 55 | end 56 | 57 | let(:rash) { Buffer::Client.new("some_token").schedules_by_profile_id(id) } 58 | 59 | it "returns a rash collection" do 60 | rash[0].class.should eq(Buffer::Schedule) 61 | end 62 | 63 | it "accesses days" do 64 | expect(rash[0].days).to include("mon") 65 | end 66 | 67 | it "accesses times" do 68 | expect(rash[0].times).to include("06:13") 69 | end 70 | end 71 | 72 | 73 | describe "#info" do 74 | before do 75 | stub_request(:get, "#{base_path}/info/configuration.json?access_token=some_token"). 76 | to_return(fixture("info.txt")) 77 | end 78 | 79 | it "connects to the correct endpoint" do 80 | subject.info 81 | end 82 | 83 | it "retrieves the correct name" do 84 | subject.info.services.twitter.types.profile.name.should eq("Twitter") 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/lib/buffer/schedule_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Buffer::Schedules do 4 | before do 5 | @schedule = JSON.parse < {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.1'}). 114 | to_return(:status => 200, :body => response, :headers => {}) 115 | client.interactions_by_update_id(id, page: 2, count: 3, event: "favorite") 116 | end 117 | end 118 | 119 | describe "#check_id" do 120 | it "fails if id is not 24 chars" do 121 | stub_request(:get, "https://api.bufferapp.com/1/updates/4eb8565e0acb04bb82000004X.json?access_token=some_token"). 122 | to_return(:status => 200, :body => "", :headers => {}) 123 | id = "4eb8565e0acb04bb82000004X" 124 | lambda { client.update_by_id(id) }. 125 | should raise_error(Buffer::Error::InvalidIdLength) 126 | end 127 | 128 | it "fails if id is not numbers and a-f" do 129 | stub_request(:get, "https://api.bufferapp.com/1/updates/4eb8565e0acb04bb8200000X.json?access_token=some_token"). 130 | to_return(:status => 200, :body => "", :headers => {}) 131 | id = "4eb8565e0acb04bb8200000X" 132 | lambda { client.update_by_id(id) }. 133 | should raise_error(Buffer::Error::InvalidIdContent) 134 | end 135 | end 136 | 137 | describe "#reorder_updates" do 138 | it "connects to appropriate endpoint" do 139 | id_no = "4ecda256512f7ee521000001" 140 | order_hash = { order: [id_no, id_no, id_no] } 141 | stub_request(:post, %r{https://api\.bufferapp\.com/1/profiles/4ecda256512f7ee521000001/updates/reorder\.json\?access_token=.*}). 142 | with(:body => {"order"=>["4ecda256512f7ee521000001", "4ecda256512f7ee521000001", "4ecda256512f7ee521000001"]}, 143 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.1'}). 144 | to_return(:status => 200, :body => reorder_updates_body_response, :headers => {}) 145 | client.reorder_updates(id_no, order_hash) 146 | end 147 | end 148 | 149 | describe "#shuffle_updates" do 150 | it "connects to appropriate endpoint" do 151 | id_no = "4ecda256512f7ee521000001" 152 | stub_request(:post, %r{https://api\.bufferapp\.com/1/profiles/4ecda256512f7ee521000001/updates/shuffle\.json\?access_token=.*}). 153 | with(:body => {"count"=>"10"}). 154 | to_return(:status => 200, :body => '{"success": true, 155 | "updates": [], 156 | "time_to_shuffle":0.0041220188140869}') 157 | client.shuffle_updates(id_no, count: 10) 158 | end 159 | end 160 | 161 | describe "#share_update" do 162 | it "should connect to correct endpoint" do 163 | stub_request(:post, %r{https://api\.bufferapp\.com/1/updates/4ecda256512f7ee521000001/share\.json\?access_token=.*}). 164 | to_return(:status => 200, :body => '{"success": true}', :headers => {}) 165 | update_id = "4ecda256512f7ee521000001" 166 | client.share_update(update_id) 167 | end 168 | end 169 | 170 | describe "#create_update" do 171 | 172 | let(:body_content) do {text: "Text for an update", 173 | profile_ids: [ 174 | "4eb854340acb04e870000010", 175 | "4eb9276e0acb04bb81000067" 176 | ]} 177 | end 178 | 179 | let(:url) { %r{https://api\.bufferapp\.com/1/updates/create\.json\?access_token=.*} } 180 | 181 | context "should create an update" do 182 | it "when only required params are present" do 183 | stub_request(:post, url). 184 | with(:body => body_content). 185 | to_return(:status => 200, :body => create_update_return_body, :headers => {}) 186 | client.create_update(body: body_content) 187 | end 188 | it "when optional params are included" do 189 | body_content[:media] = {} 190 | body_content[:media][:link] = "http://google.com" 191 | body_content[:media][:description] = "Google Homepage" 192 | stub_request(:post, url). 193 | with(:body => body_content). 194 | to_return(:status => 200, :body => create_update_return_body, :headers => {}) 195 | client.create_update(body: body_content) 196 | 197 | end 198 | end 199 | end 200 | 201 | describe "#modify_update_text" do 202 | 203 | let(:body_content) { {text: "Text for an updated text for update"} } 204 | 205 | id = "4ecda256512f7ee521000004" 206 | let(:url) { %r{https://api\.bufferapp\.com/1/updates/#{ id }/update\.json\?access_token=.*} } 207 | 208 | context "should modify an update" do 209 | it "when params are present" do 210 | stub_request(:post, url). 211 | with(:body => body_content). 212 | to_return(:status => 200, :body => modify_update_response, :headers => {}) 213 | client.modify_update_text(id, body: body_content) 214 | end 215 | end 216 | end 217 | 218 | describe "#destroy_update" do 219 | it "connects to correct endpoint" do 220 | stub_request(:post, %r{https://api\.bufferapp\.com/1/updates/4ecda256512f7ee521000001/destroy\.json\?access_token=.*}). 221 | to_return(fixture('destroy.txt')) 222 | update_id = "4ecda256512f7ee521000001" 223 | client.destroy_update(update_id) 224 | end 225 | end 226 | end 227 | end 228 | -------------------------------------------------------------------------------- /spec/lib/buffer/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Buffer::Client::User do 4 | let(:id) { "5160746d54f04a5e3a00000f" } 5 | 6 | subject do 7 | Buffer::Client.new("some_token") 8 | end 9 | 10 | describe "#user_info" do 11 | let(:rash) { subject.user_info } 12 | 13 | before(:each) do 14 | url = "#{ base_path }/user.json" 15 | stub_with_to_return(:get, url, "user_authenticated.txt") 16 | end 17 | 18 | it "returns a Rash object" do 19 | rash.class.should eq(Buffer::UserInfo) 20 | end 21 | 22 | it "provides an accessor for plan" do 23 | rash.plan.should eq("free") 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/lib/buffer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Buffer::Client do 4 | let(:id) { "5160746d54f04a5e3a00000f" } 5 | 6 | subject do 7 | Buffer::Client.new("some_token") 8 | end 9 | 10 | describe "#initialize" do 11 | it "allows a token to be set and retrieved" do 12 | subject.access_token.should eq("some_token") 13 | end 14 | end 15 | 16 | describe "#connection" do 17 | it "assigns the connection instance variable" do 18 | subject.connection.should eq(subject.instance_variable_get(:@connection)) 19 | end 20 | end 21 | 22 | describe "#info" do 23 | before do 24 | stub_request(:get, "#{base_path}/info/configuration.json?access_token=some_token"). 25 | to_return(fixture("info.txt")) 26 | end 27 | 28 | it "connects to the correct endpoint" do 29 | subject.info 30 | end 31 | 32 | it "retrieves the correct name" do 33 | subject.info.services.twitter.types.profile.name.should eq("Twitter") 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/lib/core_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Buffer::Client::Core do 4 | 5 | let(:client) { Buffer::Client.new("some_token") } 6 | describe "#get" do 7 | it "delegates to #handle_response_code when code != 200" do 8 | stub_request(:get, "#{base_path}/info/configuration.json?access_token=some_token"). 9 | to_return(:status => 403, :body => "", :headers => {}) 10 | client.should_receive(:handle_response_code).once 11 | client.info 12 | end 13 | 14 | 15 | it "does not delegate to #handle_response_code when code = 200" do 16 | stub_request(:get, "#{base_path}/info/configuration.json?access_token=some_token"). 17 | to_return(fixture("link.txt")) 18 | client.should_not_receive(:handle_response_code) 19 | client.info 20 | end 21 | end 22 | 23 | describe "#post" do 24 | 25 | it "connects to the correct endpoint" do 26 | 27 | #TODO improve test 28 | response = %Q[{"success": true, "message": "Schedule saved successfully"}] 29 | id = "4eb854340acb04e870000010" 30 | stub_request(:post, "#{ base_path }/profiles/#{id}/schedules/update.json?access_token=some_token"). 31 | with(:body => {"schedules"=>"schedules[0][days][]=mon&schedules[0][days][]=tue&schedules[0][days][]=wed&schedules[0][times][]=12%3A00&schedules[0][times][]=17%3A00&schedules[0][times][]=18%3A00"}, 32 | :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.1'}). 33 | to_return(:status => 200, :body => response, :headers => {}) 34 | client.set_schedules(id, :schedules => sample_schedules).success. 35 | should eq(true) 36 | end 37 | 38 | it "does not delegate to #handle_response_code when code = 200" do 39 | url = "#{base_path}/info/configuration.json" 40 | fixture_name = "link.txt" 41 | stub_with_to_return(:get, url, fixture_name) 42 | client.should_not_receive(:handle_response_code) 43 | client.info 44 | end 45 | 46 | end 47 | 48 | describe "#handle_response_code" do 49 | context "fails gracefully with undocumented responses" do 50 | it "responds to 401 unauthorized response" do 51 | id = "5520a65cf387f71753588135" 52 | url = "#{base_path}/updates/#{id}.json?access_token=some_token" 53 | stub_with_to_return(:get, url, "update_by_id_non_auth.txt") 54 | lambda { client.update_by_id(id) }. 55 | should raise_error(Buffer::Error::APIError) 56 | end 57 | end 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'buffer' 2 | require 'rspec' 3 | require 'webmock/rspec' 4 | require 'json' 5 | 6 | require 'coveralls' 7 | Coveralls.wear! 8 | 9 | def travis? 10 | ENV['TRAVIS_CI'] 11 | end 12 | 13 | def fixture_path 14 | File.expand_path(File.join("..", "fixtures"), __FILE__) 15 | end 16 | 17 | def fixture(file) 18 | File.new(File.join(fixture_path, file)) 19 | end 20 | 21 | def post_data 22 | < 'some_token'} 135 | end 136 | 137 | def sample_schedules 138 | [{ days: %w[mon tue wed], 139 | times: %w[12:00 17:00 18:00]}] 140 | # @sample_schedules = JSON.parse <