├── .rspec ├── spec ├── fixtures │ ├── repositories │ │ ├── test_owner │ │ │ └── test_repo │ │ │ │ ├── pullrequests │ │ │ │ └── 1 │ │ │ │ │ ├── approve │ │ │ │ │ ├── delete.json │ │ │ │ │ └── post.json │ │ │ │ │ ├── diff │ │ │ │ │ └── get.txt │ │ │ │ │ └── comments │ │ │ │ │ ├── 1 │ │ │ │ │ └── get.json │ │ │ │ │ └── get.json │ │ │ │ ├── branch-restrictions │ │ │ │ └── 1 │ │ │ │ │ └── get.json │ │ │ │ ├── commit │ │ │ │ └── 1 │ │ │ │ │ ├── approve │ │ │ │ │ └── post.json │ │ │ │ │ ├── statuses │ │ │ │ │ ├── build │ │ │ │ │ │ ├── post.json │ │ │ │ │ │ └── test_status │ │ │ │ │ │ │ ├── get.json │ │ │ │ │ │ │ └── put.json │ │ │ │ │ └── get.json │ │ │ │ │ └── comments │ │ │ │ │ ├── 1 │ │ │ │ │ └── get.json │ │ │ │ │ └── get.json │ │ │ │ ├── patch │ │ │ │ └── 1 │ │ │ │ │ └── get.json │ │ │ │ ├── watchers │ │ │ │ └── get.json │ │ │ │ ├── diff │ │ │ │ └── 1 │ │ │ │ │ └── get.json │ │ │ │ ├── get.json │ │ │ │ └── forks │ │ │ │ └── get.json │ │ └── get.json │ ├── branch_restriction.json │ ├── build_status.json │ ├── profile.json │ ├── users │ │ └── test_owner │ │ │ ├── get.json │ │ │ └── followers │ │ │ └── get.json │ ├── comment.json │ ├── teams │ │ ├── test_team │ │ │ ├── get.json │ │ │ ├── followers │ │ │ │ └── get.json │ │ │ ├── members │ │ │ │ └── get.json │ │ │ ├── projects │ │ │ │ └── myprj │ │ │ │ │ └── get.json │ │ │ └── following │ │ │ │ └── get.json │ │ └── get_role_admin.json │ └── team.json ├── lib │ ├── tinybucket │ │ ├── api │ │ │ ├── diff_api_spec.rb │ │ │ ├── repos_api_spec.rb │ │ │ ├── projects_api_spec.rb │ │ │ ├── tags_api_spec.rb │ │ │ ├── branches_api_spec.rb │ │ │ ├── user_api_spec.rb │ │ │ ├── branch_restrictions_api_spec.rb │ │ │ ├── build_status_api_spec.rb │ │ │ └── team_api_spec.rb │ │ ├── resource │ │ │ ├── base_spec.rb │ │ │ ├── teams_spec.rb │ │ │ ├── team │ │ │ │ ├── members_spec.rb │ │ │ │ ├── repos_spec.rb │ │ │ │ ├── followers_spec.rb │ │ │ │ └── following_spec.rb │ │ │ ├── user │ │ │ │ ├── repos_spec.rb │ │ │ │ ├── followers_spec.rb │ │ │ │ └── following_spec.rb │ │ │ ├── forks_spec.rb │ │ │ ├── watchers_spec.rb │ │ │ ├── pull_request │ │ │ │ ├── commits_spec.rb │ │ │ │ └── comments_spec.rb │ │ │ ├── projects_spec.rb │ │ │ ├── tags_spec.rb │ │ │ ├── commits_spec.rb │ │ │ ├── branches_spec.rb │ │ │ ├── commit │ │ │ │ ├── comments_spec.rb │ │ │ │ └── build_statuses_spec.rb │ │ │ ├── pull_requests_spec.rb │ │ │ ├── branch_restrictions_spec.rb │ │ │ └── repos_spec.rb │ │ ├── api_factory_spec.rb │ │ ├── error │ │ │ └── service_error_spec.rb │ │ ├── model │ │ │ ├── page_spec.rb │ │ │ ├── branch_restriction_spec.rb │ │ │ ├── comment_spec.rb │ │ │ ├── tag_spec.rb │ │ │ ├── project_spec.rb │ │ │ ├── branch_spec.rb │ │ │ ├── profile_spec.rb │ │ │ ├── build_status_spec.rb │ │ │ └── team_spec.rb │ │ ├── null_logger_spec.rb │ │ └── connection_spec.rb │ └── tinybucket_spec.rb ├── support │ ├── fixture_macros.rb │ └── api_response_macros.rb └── spec_helper.rb ├── lib ├── tinybucket │ ├── version.rb │ ├── error │ │ ├── conflict.rb │ │ ├── not_found.rb │ │ ├── base_error.rb │ │ └── service_error.rb │ ├── response.rb │ ├── constants.rb │ ├── error.rb │ ├── parser.rb │ ├── config.rb │ ├── resource │ │ ├── team │ │ │ ├── repos.rb │ │ │ ├── members.rb │ │ │ ├── followers.rb │ │ │ ├── following.rb │ │ │ └── base.rb │ │ ├── user │ │ │ ├── repos.rb │ │ │ ├── followers.rb │ │ │ ├── following.rb │ │ │ └── base.rb │ │ ├── commit │ │ │ ├── base.rb │ │ │ ├── comments.rb │ │ │ └── build_statuses.rb │ │ ├── teams.rb │ │ ├── pull_request │ │ │ ├── base.rb │ │ │ ├── commits.rb │ │ │ └── comments.rb │ │ ├── forks.rb │ │ ├── watchers.rb │ │ ├── tags.rb │ │ ├── repos.rb │ │ ├── branches.rb │ │ ├── base.rb │ │ ├── projects.rb │ │ ├── branch_restrictions.rb │ │ ├── pull_requests.rb │ │ └── commits.rb │ ├── model │ │ ├── concerns.rb │ │ ├── concerns │ │ │ ├── api_callable.rb │ │ │ ├── enumerable.rb │ │ │ ├── reloadable.rb │ │ │ ├── acceptable_attributes.rb │ │ │ └── repository_keys.rb │ │ ├── error_response.rb │ │ ├── project.rb │ │ ├── base.rb │ │ ├── page.rb │ │ ├── tag.rb │ │ ├── branch.rb │ │ ├── branch_restriction.rb │ │ ├── build_status.rb │ │ ├── comment.rb │ │ └── profile.rb │ ├── parser │ │ ├── object_parser.rb │ │ └── collection_parser.rb │ ├── model.rb │ ├── api.rb │ ├── api │ │ ├── helper │ │ │ ├── repos_helper.rb │ │ │ ├── tags_helper.rb │ │ │ ├── projects_helper.rb │ │ │ ├── repo_helper.rb │ │ │ ├── branches_helper.rb │ │ │ ├── branch_restrictions_helper.rb │ │ │ ├── user_helper.rb │ │ │ ├── diff_helper.rb │ │ │ ├── team_helper.rb │ │ │ ├── api_helper.rb │ │ │ ├── commits_helper.rb │ │ │ ├── build_status_helper.rb │ │ │ ├── comments_helper.rb │ │ │ └── pull_requests_helper.rb │ │ ├── helper.rb │ │ ├── projects_api.rb │ │ ├── repos_api.rb │ │ ├── base_api.rb │ │ ├── diff_api.rb │ │ ├── tags_api.rb │ │ ├── branches_api.rb │ │ ├── repo_api.rb │ │ ├── user_api.rb │ │ ├── branch_restrictions_api.rb │ │ ├── build_status_api.rb │ │ └── team_api.rb │ ├── api_factory.rb │ ├── response │ │ └── handler.rb │ ├── null_logger.rb │ ├── enumerator.rb │ ├── resource.rb │ ├── request.rb │ ├── iterator.rb │ └── connection.rb ├── tinybucket.rb └── faraday_middleware │ └── follow_oauth_redirects.rb ├── docs └── _config.yml ├── .codeclimate.yml ├── Guardfile ├── .gitignore ├── Gemfile ├── .github └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── LICENSE ├── Rakefile ├── tinybucket.gemspec └── .rubocop.yml /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/pullrequests/1/approve/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "approved": false 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/pullrequests/1/approve/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "approved": true 3 | } 4 | -------------------------------------------------------------------------------- /lib/tinybucket/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | VERSION = '1.6.0'.freeze 5 | end 6 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/diff_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::DiffApi do 4 | pending 'TODO: add specs' 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/fixture_macros.rb: -------------------------------------------------------------------------------- 1 | module FixtureMacros 2 | def load_json_fixture(suffix) 3 | JSON.load(File.read('spec/fixtures/' + suffix + '.json')) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/tinybucket/error/conflict.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Error 5 | class Conflict < ServiceError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/tinybucket/error/not_found.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Error 5 | class NotFound < ServiceError 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | encoding: utf-8 2 | title: tinybucket - A ruby client library for Bitbucket Cloud REST APIs 3 | highlighter: rouge 4 | markdown: kramdown 5 | theme: jekyll-theme-cayman 6 | -------------------------------------------------------------------------------- /lib/tinybucket/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Response 5 | extend ActiveSupport::Autoload 6 | 7 | autoload :Handler 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tinybucket/constants.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Constants 5 | MISSING_REPOSITORY_KEY = 'This method call require repository keys.'.freeze 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - ruby 8 | ratings: 9 | paths: 10 | - lib/**/*.rb 11 | exclude_paths: 12 | - spec/**/*.rb 13 | - docs/**/* 14 | -------------------------------------------------------------------------------- /lib/tinybucket/error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Error 5 | extend ActiveSupport::Autoload 6 | 7 | autoload :BaseError 8 | autoload :ServiceError 9 | autoload :Conflict 10 | autoload :NotFound 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/tinybucket/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Parser 5 | extend ActiveSupport::Autoload 6 | 7 | [ 8 | :BaseParser, 9 | :ObjectParser, 10 | :CollectionParser 11 | ].each do |klass_name| 12 | autoload klass_name 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | class Config 5 | include ActiveSupport::Configurable 6 | config_accessor :logger, :oauth_token, :oauth_secret, \ 7 | :cache_store_options, :access_token, \ 8 | :client_id, :client_secret, :user_agent 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/team/repos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Team 6 | class Repos < Tinybucket::Resource::Team::Base 7 | private 8 | 9 | def enumerator 10 | create_team_enumerator(:repos) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/user/repos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module User 6 | class Repos < Tinybucket::Resource::User::Base 7 | private 8 | 9 | def enumerator 10 | create_user_enumerator(:repos) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/error/base_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Error 5 | class BaseError < StandardError 6 | attr_reader :response_message, :response_headers 7 | 8 | def initialize(message) 9 | super message 10 | @response_message = message 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/commit/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Commit 6 | class Base < Tinybucket::Resource::Base 7 | def initialize(commit, options) 8 | @commit = commit 9 | @args = [options] 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/team/members.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Team 6 | class Members < Tinybucket::Resource::Team::Base 7 | private 8 | 9 | def enumerator 10 | create_team_enumerator(:members) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/team/followers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Team 6 | class Followers < Tinybucket::Resource::Team::Base 7 | private 8 | 9 | def enumerator 10 | create_team_enumerator(:followers) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/team/following.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Team 6 | class Following < Tinybucket::Resource::Team::Base 7 | private 8 | 9 | def enumerator 10 | create_team_enumerator(:following) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/user/followers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module User 6 | class Followers < Tinybucket::Resource::User::Base 7 | private 8 | 9 | def enumerator 10 | create_user_enumerator(:followers) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/user/following.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module User 6 | class Following < Tinybucket::Resource::User::Base 7 | private 8 | 9 | def enumerator 10 | create_user_enumerator(:following) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tinybucket/model/concerns.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | module Concerns 6 | extend ActiveSupport::Autoload 7 | 8 | [ 9 | :AcceptableAttributes, 10 | :ApiCallable, 11 | :Enumerable, 12 | :RepositoryKeys, 13 | :Reloadable 14 | ].each do |mod_name| 15 | autoload mod_name 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard 'rspec', cmd: 'rspec --drb' do 2 | watch(/^spec\/.+_spec\.rb$/) 3 | watch(/^lib\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" } 4 | watch(/^spec\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" } 5 | watch('spec/spec_helper.rb') { 'spec' } 6 | 7 | watch(/^lib\/(.+)\.rb$/) { |m| "spec/#{m[1]}_spec.rb" } 8 | end 9 | 10 | guard :rubocop, all_on_start: true, cmd: ['--format', 'clang', '-c', '.rubocop.yml'] do 11 | watch(/^lib\/(.+)\.rb$/) 12 | watch(/\.rubocop.yml$/) 13 | end 14 | -------------------------------------------------------------------------------- /lib/tinybucket/parser/object_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Parser 5 | class ObjectParser < FaradayMiddleware::ResponseMiddleware 6 | define_parser do |hash, parser_options| 7 | cls = parser_options[:model_class] 8 | throw 'model_class option does not provided' unless cls 9 | cls.new(hash) 10 | end 11 | 12 | def parse_response?(env) 13 | env[:body].is_a? Hash 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/teams.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Teams < Base 6 | def initialize(role_name, options = {}) 7 | @role_name = role_name 8 | @args = [options] 9 | end 10 | 11 | private 12 | 13 | def teams_api 14 | create_api('Team') 15 | end 16 | 17 | def enumerator 18 | create_enumerator(teams_api, :list, @role_name, *@args) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | Gemfile.lock 5 | *.bundle 6 | *.so 7 | *.o 8 | *.a 9 | mkmf.log 10 | app.rb 11 | log/ 12 | *.swp 13 | 14 | features 15 | .envrc 16 | .byebug_history 17 | .ruby-version 18 | 19 | ## from github ignore. 20 | 21 | /.config 22 | /coverage/ 23 | /InstalledFiles 24 | /pkg/ 25 | /tmp/ 26 | 27 | ## Documentation cache and generated files: 28 | /.yardoc/ 29 | /_yardoc/ 30 | /doc/ 31 | /rdoc/ 32 | 33 | ## Environment normalisation: 34 | /.bundle/ 35 | /lib/bundler/man/ 36 | /vendor/ 37 | *.iml -------------------------------------------------------------------------------- /lib/tinybucket/resource/pull_request/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module PullRequest 6 | class Base < Tinybucket::Resource::Base 7 | def initialize(pull_request, options) 8 | @pull_request = pull_request 9 | @args = [options] 10 | end 11 | 12 | protected 13 | 14 | def pull_request_api 15 | create_api('PullRequests', @pull_request.repo_keys) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/tinybucket/parser/collection_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Parser 5 | class CollectionParser < FaradayMiddleware::ResponseMiddleware 6 | define_parser do |hash, parser_options| 7 | cls = parser_options[:model_class] 8 | throw 'model_class option does not provided' unless cls 9 | ::Tinybucket::Model::Page.new(hash, cls) 10 | end 11 | 12 | def parse_response?(env) 13 | env[:body].is_a? Hash 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/tinybucket/model.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | extend ActiveSupport::Autoload 6 | 7 | [ 8 | :Base, 9 | :Branch, 10 | :BranchRestriction, 11 | :BuildStatus, 12 | :Comment, 13 | :Commit, 14 | :ErrorResponse, 15 | :Page, 16 | :Profile, 17 | :Project, 18 | :PullRequest, 19 | :Repository, 20 | :Tag, 21 | :Team 22 | ].each do |klass_name| 23 | autoload klass_name 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/fixtures/branch_restriction.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [], 3 | "id": 52, 4 | "kind": "delete", 5 | "links": [ 6 | { 7 | "href": "https://api.bitbucket.org/2.0/repositories/manthony/restrictiontest/branch-restrictions/52", 8 | "rel": "self" 9 | }, 10 | { 11 | "href": "https://api.bitbucket.org/2.0/repositories/manthony/restrictiontest/branch-restrictions", 12 | "rel": "parent" 13 | } 14 | ], 15 | "pattern": "foobar/*", 16 | "users": [] 17 | } 18 | -------------------------------------------------------------------------------- /lib/tinybucket/model/concerns/api_callable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | module Concerns 6 | module ApiCallable 7 | protected 8 | 9 | def create_api(name, keys = {}) 10 | api = ApiFactory.create_instance(name) 11 | return api if keys.empty? 12 | 13 | api.tap do |m| 14 | m.repo_owner = keys[:repo_owner] 15 | m.repo_slug = keys[:repo_slug] 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/tinybucket/model/concerns/enumerable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | module Concerns 6 | module Enumerable 7 | extend ActiveSupport::Concern 8 | 9 | included do 10 | protected 11 | 12 | def enumerator(api_client, method, *args, &block) 13 | iter = Tinybucket::Iterator.new(api_client, method, *args) 14 | Tinybucket::Enumerator.new(iter, block) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/pull_request/commits.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module PullRequest 6 | class Commits < Tinybucket::Resource::PullRequest::Base 7 | private 8 | 9 | def enumerator 10 | params = [@pull_request.id].concat(@args) 11 | 12 | create_enumerator(pull_request_api, :commits, *params) do |m| 13 | inject_repo_keys(m, @pull_request.repo_keys) 14 | end 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/forks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Forks < Base 6 | def initialize(repo, options) 7 | @repo = repo 8 | @args = [options] 9 | end 10 | 11 | private 12 | 13 | def repo_api 14 | create_api('Repo', @repo.repo_keys) 15 | end 16 | 17 | def enumerator 18 | create_enumerator(repo_api, :forks, *@args) do |m| 19 | inject_repo_keys(m, @repo.repo_keys) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class TestResource < Tinybucket::Resource::Base 4 | def enumerator 5 | (0..10).to_a.to_enum 6 | end 7 | end 8 | 9 | RSpec.describe Tinybucket::Resource::Base do 10 | let(:resource) { TestResource.new } 11 | 12 | describe 'respond_to?' do 13 | context 'with undefined method call' do 14 | subject { resource.respond_to?(:undefined_method_call) } 15 | it 'should raise NoMethodError' do 16 | expect(subject).to eq(false) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/tinybucket/api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | extend ActiveSupport::Autoload 6 | 7 | [ 8 | :BaseApi, 9 | :BranchesApi, 10 | :TagsApi, 11 | :BranchRestrictionsApi, 12 | :BuildStatusApi, 13 | :CommitsApi, 14 | :CommentsApi, 15 | :DiffApi, 16 | :ProjectsApi, 17 | :PullRequestsApi, 18 | :ReposApi, 19 | :RepoApi, 20 | :TeamApi, 21 | :UserApi 22 | ].each do |klass_name| 23 | autoload klass_name 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/repos_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module ReposHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list(options) 12 | owner = options[:owner] 13 | return base_path if owner.blank? 14 | 15 | build_path(base_path, [owner, 'owner']) 16 | end 17 | 18 | def base_path 19 | '/repositories' 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/watchers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Watchers < Base 6 | def initialize(repo, options) 7 | @repo = repo 8 | @args = [options] 9 | end 10 | 11 | private 12 | 13 | def repo_api 14 | create_api('Repo', @repo.repo_keys) 15 | end 16 | 17 | def enumerator 18 | create_enumerator(repo_api, :watchers, *@args) do |m| 19 | inject_repo_keys(m, @repo.repo_keys) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/tinybucket/api_factory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | class ApiFactory 5 | class << self 6 | def create_instance(klass_name) 7 | klass = 8 | begin 9 | name = "#{klass_name}Api".intern 10 | Tinybucket::Api.const_get name 11 | rescue => e 12 | # TODO: log exception 13 | Tinybucket.logger.error e 14 | raise ArgumentError, 'must provide klass to be instantiated' 15 | end 16 | 17 | klass.new 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/branch-restrictions/1/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [], 3 | "id": 52, 4 | "kind": "delete", 5 | "links": [ 6 | { 7 | "href": "https://api.bitbucket.org/2.0/repositories/manthony/restrictiontest/branch-restrictions/52", 8 | "rel": "self" 9 | }, 10 | { 11 | "href": "https://api.bitbucket.org/2.0/repositories/manthony/restrictiontest/branch-restrictions", 12 | "rel": "parent" 13 | } 14 | ], 15 | "pattern": "foobar/*", 16 | "users": [] 17 | } 18 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/approve/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": "PARTICIPANT", 3 | "user": { 4 | "username": "evzijst", 5 | "display_name": "Erik van Zijst", 6 | "links": { 7 | "self": { 8 | "href": "https://api.bitbucket.org/2.0/users/evzijst" 9 | }, 10 | "avatar": { 11 | "href": "https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Oct/28/evzijst-avatar-3454044670-3_avatar.png" 12 | } 13 | } 14 | }, 15 | "approved": true 16 | } 17 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api_factory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::ApiFactory do 4 | 5 | describe 'create_instance' do 6 | subject { Tinybucket::ApiFactory.create_instance(klass_name) } 7 | 8 | context 'with valid klass_name' do 9 | let(:klass_name) { 'PullRequests' } 10 | it { expect(subject).to be_a_kind_of(Tinybucket::Api::BaseApi) } 11 | end 12 | 13 | context 'with invalid klass_name' do 14 | let(:klass_name) { 'Invalid Klass Name' } 15 | it { expect { subject }.to raise_error(ArgumentError) } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/team/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Team 6 | class Base < Tinybucket::Resource::Base 7 | def initialize(team_name, options) 8 | @team_name = team_name 9 | @args = [options] 10 | end 11 | 12 | protected 13 | 14 | def team_api 15 | create_api('Team') 16 | end 17 | 18 | def create_team_enumerator(method) 19 | create_enumerator(team_api, method, @team_name, *@args) 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/fixtures/build_status.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "SUCCESSFUL", 3 | "type": "build", 4 | "key": "test_status", 5 | "name": "Build #34", 6 | "url": "https://example.com/path/to/build", 7 | "description": "Changes by John Doe", 8 | "links": { 9 | "self": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466/statuses/build/BAMBOO-PROJECT-X" 11 | }, 12 | "commit": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/user/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module User 6 | class Base < Tinybucket::Resource::Base 7 | def initialize(user_name, options) 8 | @user_name = user_name 9 | @args = [options] 10 | end 11 | 12 | protected 13 | 14 | def user_api 15 | create_api('User').tap do |api| 16 | api.username = @user_name 17 | end 18 | end 19 | 20 | def create_user_enumerator(method) 21 | create_enumerator(user_api, method, *@args) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | extend ActiveSupport::Autoload 7 | 8 | [ 9 | :ApiHelper, 10 | :BranchesHelper, 11 | :BranchRestrictionsHelper, 12 | :TagsHelper, 13 | :BuildStatusHelper, 14 | :CommitsHelper, 15 | :CommentsHelper, 16 | :DiffHelper, 17 | :ReposHelper, 18 | :RepoHelper, 19 | :PullRequestsHelper, 20 | :ProjectsHelper, 21 | :TeamHelper, 22 | :UserHelper 23 | ].each do |klass_name| 24 | autoload klass_name 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/tinybucket/response/handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Response 5 | class Handler < Faraday::Response::Middleware 6 | def on_complete(env) 7 | status_code = env[:status].to_i 8 | 9 | return if status_code < 400 10 | 11 | case status_code 12 | when 404 13 | raise Tinybucket::Error::NotFound.new(env) 14 | when 409 15 | raise Tinybucket::Error::Conflict.new(env) 16 | else 17 | Tinybucket.logger.error "Invalid response code:#{status_code}" 18 | raise Tinybucket::Error::ServiceError.new(env) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tinybucket/null_logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | class NullLogger 5 | attr_accessor :level 6 | 7 | def fatal(_progname = nil, &_block); end 8 | 9 | def fatal? 10 | false 11 | end 12 | 13 | def error(_progname = nil, &_block); end 14 | 15 | def error? 16 | false 17 | end 18 | 19 | def warn(_progname = nil, &_block); end 20 | 21 | def warn? 22 | false 23 | end 24 | 25 | def info(_progname = nil, &_block); end 26 | 27 | def info? 28 | false 29 | end 30 | 31 | def debug(_progname = nil, &_block); end 32 | 33 | def debug? 34 | false 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/statuses/build/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "SUCCESSFUL", 3 | "type": "build", 4 | "key": "BAMBOO-PROJECT-X", 5 | "name": "Build #34", 6 | "url": "https://example.com/path/to/build", 7 | "description": "Changes by John Doe", 8 | "links": { 9 | "self": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466/statuses/build/BAMBOO-PROJECT-X" 11 | }, 12 | "commit": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/tags_helper.rb: -------------------------------------------------------------------------------- 1 | module Tinybucket 2 | module Api 3 | module Helper 4 | module TagsHelper 5 | include ::Tinybucket::Api::Helper::ApiHelper 6 | 7 | private 8 | 9 | def path_to_list 10 | build_path(base_path) 11 | end 12 | 13 | def path_to_find(tag) 14 | build_path(base_path, 15 | [tag, 'tag']) 16 | end 17 | 18 | def base_path 19 | build_path('/repositories', 20 | [repo_owner, 'repo_owner'], 21 | [repo_slug, 'repo_slug'], 22 | 'refs', 'tags') 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in bitbucket.gemspec 4 | gemspec 5 | 6 | gem 'simple-auth', '~> 0.5.0' 7 | gem 'yard', '~> 0.9.12' 8 | gem 'yardstick', '~> 0.9.9' 9 | 10 | group :development, :test do 11 | gem 'pry' 12 | gem 'pry-stack_explorer' 13 | gem 'pry-byebug' 14 | gem 'pry-rescue' 15 | 16 | gem 'guard-rspec' 17 | gem 'guard-spork' 18 | 19 | gem 'guard-rubocop' 20 | gem 'gem-release' 21 | 22 | gem 'rake', '~> 13.0' 23 | gem 'rspec', '~> 3.4' 24 | gem 'rspec-mocks', '~> 3.4' 25 | gem 'webmock', '~> 3.12.1' 26 | gem 'rubocop', '~> 1.11.0' 27 | end 28 | 29 | group :test do 30 | gem 'simplecov', '~> 0.21.2', require: false 31 | end 32 | -------------------------------------------------------------------------------- /lib/tinybucket/api/projects_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | class ProjectsApi < BaseApi 6 | include Tinybucket::Api::Helper::ProjectsHelper 7 | attr_accessor :owner 8 | 9 | def list(options = {}) 10 | get_path( 11 | path_to_list(owner), 12 | options, 13 | get_parser(:collection, Tinybucket::Model::Project) 14 | ) 15 | end 16 | 17 | def find(project_key, options = {}) 18 | get_path( 19 | path_to_find(owner, project_key), 20 | options, 21 | get_parser(:object, Tinybucket::Model::Project) 22 | ) 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/statuses/build/test_status/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "SUCCESSFUL", 3 | "type": "build", 4 | "key": "BAMBOO-PROJECT-X", 5 | "name": "Build #34", 6 | "url": "https://example.com/path/to/build", 7 | "description": "Changes by John Doe", 8 | "links": { 9 | "self": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466/statuses/build/BAMBOO-PROJECT-X" 11 | }, 12 | "commit": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/statuses/build/test_status/put.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "SUCCESSFUL", 3 | "type": "build", 4 | "key": "BAMBOO-PROJECT-X", 5 | "name": "Build #34", 6 | "url": "https://example.com/path/to/build", 7 | "description": "Changes by John Doe", 8 | "links": { 9 | "self": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466/statuses/build/BAMBOO-PROJECT-X" 11 | }, 12 | "commit": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/statuses/get.json: -------------------------------------------------------------------------------- 1 | {"pagelen": 10, "values": [{ 2 | "state": "SUCCESSFUL", 3 | "type": "build", 4 | "key": "BAMBOO-PROJECT-X", 5 | "name": "Build #34", 6 | "url": "https://example.com/path/to/build", 7 | "description": "Changes by John Doe", 8 | "links": { 9 | "self": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466/statuses/build/BAMBOO-PROJECT-X" 11 | }, 12 | "commit": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/emmap1/MyRepo/commits/61d9e64348f9da407e62f64726337fd3bb24b466" 14 | } 15 | } 16 | }]} 17 | -------------------------------------------------------------------------------- /lib/tinybucket/model/error_response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # ErrorResponse 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid#stand-error 8 | # Standardized error responses 9 | # 10 | # @!attribute [rw] message 11 | # @return [String] 12 | # @!attribute [rw] fields 13 | # @return [Hash] 14 | # @!attribute [rw] detail 15 | # @return [String] 16 | # @!attribute [rw] id 17 | # @return [String] 18 | # @!attribute [rw] uuid 19 | # @return [NillClass] 20 | class ErrorResponse 21 | acceptable_attributes :message, :fields, :detail, :id, :uuid 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/projects_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module ProjectsHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list(owner) 12 | build_path(base_path(owner), '/') 13 | end 14 | 15 | def path_to_find(owner, project_key) 16 | build_path(base_path(owner), 17 | [project_key, 'project_key']) 18 | end 19 | 20 | def base_path(owner) 21 | build_path('/teams', 22 | [owner, 'owner'], 23 | 'projects') 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/repo_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module RepoHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_find 12 | base_path 13 | end 14 | 15 | def path_to_watchers 16 | build_path(base_path, '/watchers') 17 | end 18 | 19 | def path_to_forks 20 | build_path(base_path, '/forks') 21 | end 22 | 23 | def base_path 24 | build_path('/repositories', 25 | [repo_owner, 'repo_owner'], 26 | [repo_slug, 'repo_slug']) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | 4 | require 'rspec' 5 | require 'webmock/rspec' 6 | 7 | require 'pry' 8 | 9 | require 'simplecov' 10 | SimpleCov.start do 11 | add_filter '.bundle/' 12 | add_filter 'spec' 13 | add_filter 'features' 14 | end 15 | 16 | require 'tinybucket' 17 | 18 | path = Pathname.new(Dir.pwd) 19 | Dir[path.join('spec/support/**/*.rb')].each { |f| require f } 20 | 21 | # configure tinybucket logger. 22 | Dir.mkdir('log') unless Dir.exist?('log') 23 | 24 | logger = Logger.new('log/test.log') 25 | logger.level = Logger::DEBUG 26 | Tinybucket.logger = logger 27 | 28 | RSpec.configure do |config| 29 | config.extend FixtureMacros 30 | config.include FixtureMacros 31 | config.order = 'random' 32 | end 33 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/branches_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module BranchesHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | build_path(base_path) 13 | end 14 | 15 | def path_to_find(branch) 16 | build_path(base_path, 17 | [branch, 'branch']) 18 | end 19 | 20 | def base_path 21 | build_path('/repositories', 22 | [repo_owner, 'repo_owner'], 23 | [repo_slug, 'repo_slug'], 24 | 'refs', 'branches') 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/tinybucket/error/service_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Error 5 | class ServiceError < BaseError 6 | attr_accessor :http_headers 7 | attr_reader :http_method, :request_url, :status_code, :response_body 8 | 9 | def initialize(env) 10 | super generate_message(env) 11 | @http_headers = env[:response_headers] 12 | end 13 | 14 | private 15 | 16 | def generate_message(env) 17 | @http_method = env[:method].to_s.upcase 18 | @request_url = env[:url].to_s 19 | @status_code = env[:status] 20 | @response_body = env[:body] 21 | 22 | "#{@http_method} #{@request_url} #{@status_code} #{@response_body}" 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/teams_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Teams do 4 | include ApiResponseMacros 5 | 6 | let(:role_name) { "admin" } 7 | let(:resource) { Tinybucket::Resource::Teams.new(role_name) } 8 | 9 | describe "Enumerable Methods" do 10 | let(:request_path) { "/teams?role=#{role_name}" } 11 | before { stub_enum_response(:get, request_path) } 12 | 13 | describe "#take(1)" do 14 | subject { resource.take(1) } 15 | it { expect(subject).to be_an_instance_of(Array) } 16 | end 17 | 18 | describe "#each" do 19 | it 'iterate models' do 20 | resource.each do |m| 21 | expect(m).to be_an_instance_of(Tinybucket::Model::Team) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/branch_restrictions_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module BranchRestrictionsHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | base_path 13 | end 14 | 15 | def path_to_find(restriction_id) 16 | build_path(base_path, 17 | [restriction_id, 'restriction_id']) 18 | end 19 | 20 | def base_path 21 | build_path('/repositories', 22 | [repo_owner, 'repo_owner'], 23 | [repo_slug, 'repo_slug'], 24 | 'branch-restrictions') 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/user_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module UserHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_find 12 | base_path 13 | end 14 | 15 | def path_to_followers 16 | build_path(base_path, 'followers') 17 | end 18 | 19 | def path_to_following 20 | build_path(base_path, 'following') 21 | end 22 | 23 | def path_to_repos 24 | build_path('/repositories', [username, 'username']) 25 | end 26 | 27 | def base_path 28 | build_path('/users', [username, 'username']) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/pullrequests/1/diff/get.txt: -------------------------------------------------------------------------------- 1 | diff --git a/apps/account/forms.py b/apps/account/forms.py 2 | index c2f3d2d..2c27220 100644 3 | --- a/apps/account/forms.py 4 | +++ b/apps/account/forms.py 5 | @@ -553,6 +553,8 @@ class UsernameChangeForm(forms.Form): 6 | raise TypeError("Keyword argument 'request' must be supplied") 7 | super(UsernameChangeForm, self).__init__(*args, **kwargs) 8 | self.request = request 9 | + if request.target_user.get_profile().is_team: 10 | + self.fields['username'].label = _('Team id') 11 | if request.user == request.target_user: 12 | self.fields['username'].help_text = _('You will need to log in ' 13 | 'again after making this ' 14 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/error/service_error_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Error::ServiceError do 4 | context 'initialize' do 5 | let(:env) do 6 | { 7 | response_headers: [], 8 | method: 'POST', 9 | url: 'https://api.example.org/path/to', 10 | status: 500, 11 | body: 'Internal Server Error' 12 | } 13 | end 14 | 15 | subject { Tinybucket::Error::ServiceError.new(env) } 16 | 17 | it { expect(subject).to be_an_instance_of(Tinybucket::Error::ServiceError) } 18 | it { expect(subject.http_method).to eq(env[:method]) } 19 | it { expect(subject.request_url).to eq(env[:url]) } 20 | it { expect(subject.status_code).to eq(env[:status]) } 21 | it { expect(subject.response_body).to eq(env[:body]) } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/diff_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module DiffHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_find(spec) 12 | build_path(base_path, 13 | 'diff', 14 | [spec, 'spec']) 15 | end 16 | 17 | def path_to_patch(spec) 18 | build_path(base_path, 19 | 'patch', 20 | [spec, 'spec']) 21 | end 22 | 23 | def base_path 24 | build_path('/repositories', 25 | [repo_owner, 'repo_owner'], 26 | [repo_slug, 'repo_slug']) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/team/members_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Team::Members do 4 | include ApiResponseMacros 5 | 6 | let(:team) { 'test_team' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::Team::Members.new(team, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/teams/#{team}/members" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Team) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/team/repos_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::User::Repos do 4 | include ApiResponseMacros 5 | 6 | let(:team) { 'test_team' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::Team::Repos.new(team, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/teams/#{team}/repositories" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Repository) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/user/repos_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::User::Repos do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::User::Repos.new(owner, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/repositories/#{owner}" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Repository) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/team/followers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Team::Followers do 4 | include ApiResponseMacros 5 | 6 | let(:team) { 'test_team' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::Team::Followers.new(team, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/teams/#{team}/followers" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Team) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/team/following_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Team::Following do 4 | include ApiResponseMacros 5 | 6 | let(:team) { 'test_team' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::Team::Following.new(team, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/teams/#{team}/following" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Team) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/tinybucket/api/repos_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # Repos Api client 6 | class ReposApi < BaseApi 7 | include Tinybucket::Api::Helper::ReposHelper 8 | 9 | # Send 'GET a list of repositories for an account' request 10 | # 11 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories 12 | # GET a list of repositories for an account 13 | # 14 | # @param options [Hash] 15 | # @return [Tinybucket::Model::Page] 16 | def list(options = {}) 17 | opts = options.clone 18 | opts.delete(:owner) 19 | 20 | get_path( 21 | path_to_list(options), 22 | opts, 23 | get_parser(:collection, Tinybucket::Model::Repository) 24 | ) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/user/followers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::User::Followers do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::User::Followers.new(owner, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/users/#{owner}/followers" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Profile) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/user/following_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::User::Following do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::User::Following.new(owner, options) } 9 | 10 | describe 'Enumerable Methods' do 11 | let(:request_path) { "/users/#{owner}/following" } 12 | before { stub_enum_response(:get, request_path) } 13 | 14 | describe '#take(1)' do 15 | subject { resource.take(1) } 16 | it { expect(subject).to be_an_instance_of(Array) } 17 | end 18 | 19 | describe '#each' do 20 | it 'iterate models' do 21 | resource.each do |m| 22 | expect(m).to be_an_instance_of(Tinybucket::Model::Profile) 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/repos_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | RSpec.describe Tinybucket::Api::ReposApi do 4 | include ApiResponseMacros 5 | 6 | let(:api) { Tinybucket::Api::ReposApi.new } 7 | 8 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 9 | 10 | describe 'list' do 11 | subject { api.list(options) } 12 | 13 | before { stub_apiresponse(:get, request_path) } 14 | 15 | context 'without owner' do 16 | let(:options) { {} } 17 | let(:request_path) { '/repositories' } 18 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 19 | end 20 | 21 | context 'with owner' do 22 | let(:options) { { owner: 'test_owner' } } 23 | let(:request_path) { '/repositories/test_owner' } 24 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/tags.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Tags < Tinybucket::Resource::Base 6 | def initialize(repo, options) 7 | @repo = repo 8 | @args = [options] 9 | end 10 | 11 | # Find the tag 12 | # 13 | # @param tag [String] 14 | # @param options [Hash] 15 | # @return [Tinybucket::Model::Tag] 16 | def find(tag, options = {}) 17 | tags_api.find(tag, options).tap do |m| 18 | inject_repo_keys(m, @repo.repo_keys) 19 | end 20 | end 21 | 22 | private 23 | 24 | def tags_api 25 | create_api('Tags', @repo.repo_keys) 26 | end 27 | 28 | def enumerator 29 | create_enumerator(tags_api, :list, *@args) do |m| 30 | inject_repo_keys(m, @repo.repo_keys) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/tinybucket/model/concerns/reloadable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | module Concerns 6 | module Reloadable 7 | extend ActiveSupport::Concern 8 | 9 | included do 10 | def load 11 | return true if @_loaded 12 | 13 | self.attributes = load_model.attributes 14 | @_loaded = true 15 | rescue => e 16 | @_loaded = false 17 | Tinybucket.logger.error e 18 | raise e 19 | end 20 | 21 | def loaded? 22 | @_loaded 23 | end 24 | 25 | def reload 26 | @_loaded = false 27 | 28 | self.load 29 | end 30 | 31 | private 32 | 33 | def load_model 34 | raise NotImplementedError 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/repos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Repos < Base 6 | def initialize(owner, options) 7 | @owner = owner 8 | @args = [options] 9 | end 10 | 11 | def create(_options) 12 | raise NotImplementedError 13 | end 14 | 15 | def find(_options) 16 | raise NotImplementedError 17 | end 18 | 19 | private 20 | 21 | def user_api 22 | create_api('User').tap do |api| 23 | api.username = @owner 24 | end 25 | end 26 | 27 | def repos_api 28 | create_api('Repos') 29 | end 30 | 31 | def enumerator 32 | if @owner 33 | create_enumerator(user_api, :repos, *@args) 34 | else 35 | create_enumerator(repos_api, :list, *@args) 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/page_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Page do 4 | 5 | let(:json) do 6 | text = File.read('spec/fixtures/repositories/get.json') 7 | JSON.parse(text) 8 | end 9 | 10 | let(:klass) { Tinybucket::Model::Repository } 11 | let(:model) { Tinybucket::Model::Page.new(json, klass) } 12 | 13 | describe 'initialize' do 14 | subject { model } 15 | 16 | it 'create instance' do 17 | expect(subject).to be_an_instance_of(Tinybucket::Model::Page) 18 | 19 | expect(subject.attrs[:size]).to eq(json['size']) 20 | expect(subject.attrs[:page]).to eq(json['page']) 21 | expect(subject.attrs[:pagelen]).to eq(json['pagelen']) 22 | expect(subject.attrs[:next]).to eq(json['next']) 23 | expect(subject.attrs[:previous]).to eq(json['previous']) 24 | 25 | expect(subject.items.size).to eq(json['values'].size) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/branches.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Branches < Tinybucket::Resource::Base 6 | def initialize(repo, options) 7 | @repo = repo 8 | @args = [options] 9 | end 10 | 11 | # Find the branch 12 | # 13 | # @param branch [String] 14 | # @param options [Hash] 15 | # @return [Tinybucket::Model::Branch] 16 | def find(branch, options = {}) 17 | branches_api.find(branch, options).tap do |m| 18 | inject_repo_keys(m, @repo.repo_keys) 19 | end 20 | end 21 | 22 | private 23 | 24 | def branches_api 25 | create_api('Branches', @repo.repo_keys) 26 | end 27 | 28 | def enumerator 29 | create_enumerator(branches_api, :list, *@args) do |m| 30 | inject_repo_keys(m, @repo.repo_keys) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/projects_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::ProjectsApi do 4 | include ApiResponseMacros 5 | 6 | let(:api) { Tinybucket::Api::ProjectsApi.new } 7 | let(:owner) { 'test_team' } 8 | let(:request_path) { nil } 9 | before do 10 | api.owner = owner 11 | stub_apiresponse(:get, request_path) if request_path 12 | end 13 | 14 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 15 | 16 | describe 'list' do 17 | subject { api.list() } 18 | let(:request_path) { '/teams/test_team/projects/' } 19 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 20 | end 21 | 22 | describe 'find' do 23 | let(:project_key) { 'myprj' } 24 | subject { api.find(project_key) } 25 | 26 | let(:request_path) { "/teams/test_team/projects/#{project_key}" } 27 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Project) } 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/pull_request/comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module PullRequest 6 | class Comments < Tinybucket::Resource::PullRequest::Base 7 | # Get the specific comment on the pull request. 8 | # 9 | # @param comment_id [String] 10 | # @param options [Hash] 11 | # @return [Tinybucket::Model::Comment] 12 | def find(comment_id, options) 13 | comment_api.find(comment_id, options) 14 | end 15 | 16 | private 17 | 18 | def comment_api 19 | create_api('Comments', @pull_request.repo_keys).tap do |api| 20 | api.commented_to = @pull_request 21 | end 22 | end 23 | 24 | def enumerator 25 | create_enumerator(comment_api, :list, *@args) do |m| 26 | inject_repo_keys(m, @pull_request.repo_keys) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/tinybucket/model/concerns/acceptable_attributes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | module Concerns 6 | module AcceptableAttributes 7 | extend ActiveSupport::Concern 8 | 9 | included do 10 | def self.acceptable_attributes(*attrs) 11 | @_acceptable_attributes = attrs.map(&:intern) 12 | 13 | attr_accessor(*attrs) 14 | end 15 | 16 | def self.acceptable_attribute?(key) 17 | return false if @_acceptable_attributes.nil? 18 | 19 | @_acceptable_attributes.include?(key.intern) 20 | end 21 | 22 | protected 23 | 24 | def acceptable_attribute?(key) 25 | self.class.acceptable_attribute?(key) 26 | end 27 | 28 | def acceptable_attributes 29 | self.class.instance_variable_get(:@_acceptable_attributes) || [] 30 | end 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "tutorials", 3 | "kind": "user", 4 | "website": "https://tutorials.bitbucket.org/", 5 | "display_name": "first name last", 6 | "links": { 7 | "self": { 8 | "href": "https://api.bitbucket.org/2.0/users/tutorials" 9 | }, 10 | "repositories": { 11 | "href": "https://api.bitbucket.org/2.0/users/tutorials/repositories" 12 | }, 13 | "html": { 14 | "href": "https://api.bitbucket.org/tutorials" 15 | }, 16 | "followers": { 17 | "href": "https://api.bitbucket.org/2.0/users/tutorials/followers" 18 | }, 19 | "avatar": { 20 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 21 | }, 22 | "following": { 23 | "href": "https://api.bitbucket.org/2.0/users/tutorials/following" 24 | } 25 | }, 26 | "created_on": "2011-12-20T16:34:07.132459+00:00", 27 | "location": "San Francisco, CA", 28 | "type": "user" 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ pull_request, push ] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ ubuntu-latest ] 11 | ruby: [ '3.1'] 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | ruby-version: ${{ matrix.ruby }} 20 | bundler-cache: true 21 | cache-version: ${{ runner.os }}-${{ runner.ruby }}-${{ hashFiles('Gemfile.lock', 'tinybucket.gemspec') }} 22 | - name: Install dependencies 23 | run: bundle install 24 | - name: Run spec & publish coverage 25 | uses: paambaati/codeclimate-action@v3.2.0 26 | env: 27 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 28 | with: 29 | coverageCommand: bundle exec rake spec 30 | coverageLocations: ${{ github.workspace }}/coverage/coverage.json:simplecov 31 | -------------------------------------------------------------------------------- /spec/fixtures/users/test_owner/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "tutorials", 3 | "kind": "user", 4 | "website": "https://tutorials.bitbucket.org/", 5 | "display_name": "first name last", 6 | "links": { 7 | "self": { 8 | "href": "https://api.bitbucket.org/2.0/users/tutorials" 9 | }, 10 | "repositories": { 11 | "href": "https://api.bitbucket.org/2.0/users/tutorials/repositories" 12 | }, 13 | "html": { 14 | "href": "https://api.bitbucket.org/tutorials" 15 | }, 16 | "followers": { 17 | "href": "https://api.bitbucket.org/2.0/users/tutorials/followers" 18 | }, 19 | "avatar": { 20 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 21 | }, 22 | "following": { 23 | "href": "https://api.bitbucket.org/2.0/users/tutorials/following" 24 | } 25 | }, 26 | "created_on": "2011-12-20T16:34:07.132459+00:00", 27 | "location": "San Francisco, CA", 28 | "type": "user" 29 | } 30 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/branch_restriction_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::BranchRestriction do 4 | include ApiResponseMacros 5 | 6 | let(:model_json) { load_json_fixture('branch_restriction') } 7 | 8 | let(:request_method) { :get } 9 | let(:request_path) { nil } 10 | 11 | let(:owner) { 'test_owner' } 12 | let(:slug) { 'test_repo' } 13 | 14 | let(:model) do 15 | m = Tinybucket::Model::BranchRestriction.new(model_json) 16 | m.repo_owner = owner 17 | m.repo_slug = slug 18 | 19 | m 20 | end 21 | 22 | before { stub_apiresponse(:get, request_path, stub_options) if request_path } 23 | 24 | it_behaves_like 'model has acceptable_attributes', 25 | Tinybucket::Model::BranchRestriction, 26 | load_json_fixture('branch_restriction') 27 | 28 | describe '#update' do 29 | pending 'TODO implement method' 30 | end 31 | 32 | describe '#destroy' do 33 | pending 'TODO implement method' 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Comment do 4 | include ApiResponseMacros 5 | include ModelMacros 6 | 7 | let(:model_json) { load_json_fixture('comment') } 8 | 9 | let(:owner) { 'test_owner' } 10 | let(:slug) { 'test_repo' } 11 | 12 | let(:commit) do 13 | m = Tinybucket::Model::Commit.new({}) 14 | m.repo_owner = owner 15 | m.repo_slug = slug 16 | m.hash = '1' 17 | m 18 | end 19 | 20 | it_behaves_like 'model has acceptable_attributes', 21 | Tinybucket::Model::Comment, 22 | load_json_fixture('comment') 23 | 24 | describe 'model can reloadable' do 25 | let(:comment) do 26 | m = Tinybucket::Model::Comment.new({}) 27 | m.repo_owner = owner 28 | m.repo_slug = slug 29 | m.commented_to = commit 30 | m.id = '1' 31 | m 32 | end 33 | 34 | before { @model = commit } 35 | it_behaves_like 'the model is reloadable' 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Base 6 | include Tinybucket::Model::Concerns::ApiCallable 7 | 8 | protected 9 | 10 | def method_missing(method, *args) 11 | enum = enumerator 12 | return super unless enum.respond_to?(method) 13 | 14 | enum.send(method, *args) do |m| 15 | block_given? ? yield(m) : m 16 | end 17 | end 18 | 19 | def respond_to_missing?(symbol, include_all) 20 | enumerator.respond_to?(symbol, include_all) 21 | end 22 | 23 | def create_enumerator(api_client, method, *args, &block) 24 | iter = Tinybucket::Iterator.new(api_client, method, *args) 25 | Tinybucket::Enumerator.new(iter, block) 26 | end 27 | 28 | def inject_repo_keys(model, repo_keys) 29 | return model unless model.respond_to?(:repo_keys=) 30 | 31 | model.tap { |m| m.repo_keys = repo_keys } 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/fixtures/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": { 4 | "href": "https://bitbucket.org/api/2.0/repositories/tutorials/tutorials.bitbucket.org/pullrequests/2821/comments/839163" 5 | }, 6 | "html": { 7 | "href": "https://bitbucket.org/tutorials/tutorials.bitbucket.org/pull-request/2821/_/diff#comment-839163" 8 | } 9 | }, 10 | "content": { 11 | "raw": "this is my first comment", 12 | "markup": "markdown", 13 | "html": "

this is my first comment

" 14 | }, 15 | "created_on": "2013-11-19T21:19:24.138375+00:00", 16 | "user": { 17 | "username": "tutorials", 18 | "display_name": "first name last", 19 | "links": { 20 | "self": { 21 | "href": "https://bitbucket.org/api/2.0/users/tutorials" 22 | }, 23 | "avatar": { 24 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 25 | } 26 | } 27 | }, 28 | "updated_on": "2013-11-19T21:19:24.141013+00:00", 29 | "id": 839163 30 | } 31 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/forks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Forks do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | 9 | let(:options) { {} } 10 | let(:repo) do 11 | Tinybucket::Model::Repository.new({}).tap do |m| 12 | m.repo_owner = owner 13 | m.repo_slug = slug 14 | end 15 | end 16 | 17 | let(:resource) { Tinybucket::Resource::Forks.new(repo, options) } 18 | 19 | describe 'Enumerable Methods' do 20 | let(:request_path) { "/repositories/#{owner}/#{slug}/forks" } 21 | before { stub_enum_response(:get, request_path) } 22 | 23 | describe '#take(1)' do 24 | subject { resource.take(1) } 25 | it { expect(subject).to be_an_instance_of(Array) } 26 | end 27 | 28 | describe '#each' do 29 | it 'iterate models' do 30 | resource.each do |m| 31 | expect(m).to be_an_instance_of(Tinybucket::Model::Repository) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/commit/comments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Commit 6 | class Comments < Tinybucket::Resource::Commit::Base 7 | # Get the specific commit comment which associate with the commit. 8 | # 9 | # @param comment_id [String] 10 | # @param options [Hash] 11 | # @return [Tinybucket::Model::Comment] 12 | def find(comment_id, options = {}) 13 | comments_api.find(comment_id, options).tap do |m| 14 | m.repo_keys = @commit.repo_keys 15 | end 16 | end 17 | 18 | private 19 | 20 | def comments_api 21 | create_api('Comments', @commit.repo_keys).tap do |api| 22 | api.commented_to = @commit 23 | end 24 | end 25 | 26 | def enumerator 27 | create_enumerator(comments_api, :list, *@args) do |m| 28 | inject_repo_keys(m, @commit.repo_keys) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/patch/1/get.json: -------------------------------------------------------------------------------- 1 | # HG changeset patch 2 | # User Juan Borda 3 | # Date 1384880449 18000 4 | # Node ID 6247bd81bdc2d254a6be8b00f28137aec7225cc2 5 | # Parent 68599fa0e4054272d09e2e69a0f3beb9af903192 6 | add churchill quote 7 | 8 | diff --git a/index.html b/index.html 9 | --- a/index.html 10 | +++ b/index.html 11 | @@ -55,7 +55,17 @@ 12 | --> 13 | 14 | 15 | - 16 | + 17 | + 18 | + 19 | + 20 | + 21 | +

"If you're going thru hell, keep going."

22 | + Wiston Churchill 23 | +
24 | + 25 | + 26 | + 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/pullrequests/1/comments/1/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": { 4 | "href": "https://bitbucket.org/api/2.0/repositories/tutorials/tutorials.bitbucket.org/pullrequests/2821/comments/839163" 5 | }, 6 | "html": { 7 | "href": "https://bitbucket.org/tutorials/tutorials.bitbucket.org/pull-request/2821/_/diff#comment-839163" 8 | } 9 | }, 10 | "content": { 11 | "raw": "this is my first comment", 12 | "markup": "markdown", 13 | "html": "

this is my first comment

" 14 | }, 15 | "created_on": "2013-11-19T21:19:24.138375+00:00", 16 | "user": { 17 | "username": "tutorials", 18 | "display_name": "first name last", 19 | "links": { 20 | "self": { 21 | "href": "https://bitbucket.org/api/2.0/users/tutorials" 22 | }, 23 | "avatar": { 24 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 25 | } 26 | } 27 | }, 28 | "updated_on": "2013-11-19T21:19:24.141013+00:00", 29 | "id": 839163 30 | } 31 | -------------------------------------------------------------------------------- /spec/fixtures/teams/test_team/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "1team", 3 | "kind": "team", 4 | "website": "", 5 | "display_name": "the team", 6 | "links": { 7 | "self": { 8 | "href": "https://api.bitbucket.org/2.0/teams/1team" 9 | }, 10 | "repositories": { 11 | "href": "https://api.bitbucket.org/2.0/teams/1team/repositories" 12 | }, 13 | "html": { 14 | "href": "https://api.bitbucket.org/1team" 15 | }, 16 | "followers": { 17 | "href": "https://api.bitbucket.org/2.0/teams/1team/followers" 18 | }, 19 | "avatar": { 20 | "href": "https://secure.gravatar.com/avatar/24b6be68b53e383c5d42bab4fe0bde2b?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Fbc85d1577e04%2Fimg%2Fdefault_team_avatar%2F32%2Fteam_blue.png&s=32" 21 | }, 22 | "members": { 23 | "href": "https://api.bitbucket.org/2.0/teams/1team/members" 24 | }, 25 | "following": { 26 | "href": "https://api.bitbucket.org/2.0/teams/1team/following" 27 | } 28 | }, 29 | "created_on": "2013-04-22T18:09:29.103337+00:00", 30 | "location": "", 31 | "type": "team" 32 | } 33 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/watchers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Watchers do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:repo) do 9 | Tinybucket::Model::Repository.new({}).tap do |m| 10 | m.repo_owner = owner 11 | m.repo_slug = slug 12 | end 13 | end 14 | 15 | let(:options) { {} } 16 | let(:resource) { Tinybucket::Resource::Watchers.new(repo, options) } 17 | 18 | describe 'Enumerable Methods' do 19 | let(:params) { {} } 20 | let(:request_path) do 21 | "/repositories/#{owner}/#{slug}/watchers" 22 | end 23 | before { stub_enum_response(:get, request_path) } 24 | 25 | describe '#take(1)' do 26 | subject { resource.take(1) } 27 | it { expect(subject).to be_an_instance_of(Array) } 28 | end 29 | 30 | describe '#each' do 31 | it 'iterate models' do 32 | resource.each do |m| 33 | expect(m).to be_an_instance_of(Tinybucket::Model::Profile) 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/team_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module TeamHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | base_path 13 | end 14 | 15 | def path_to_find(name) 16 | team_path(name) 17 | end 18 | 19 | def path_to_members(name) 20 | build_path(team_path(name), 'members') 21 | end 22 | 23 | def path_to_followers(name) 24 | build_path(team_path(name), 'followers') 25 | end 26 | 27 | def path_to_following(name) 28 | build_path(team_path(name), 'following') 29 | end 30 | 31 | def path_to_repos(name) 32 | build_path(team_path(name), 'repositories') 33 | end 34 | 35 | def base_path 36 | build_path('/teams') 37 | end 38 | 39 | def team_path(name) 40 | build_path(base_path, [name, 'teamname']) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/watchers/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 2, 3 | "next": "https://api.bitbucket.org/2.0/repositories/test_owner/test_repo/watchers?pagelen=2&page=2", 4 | "values": [ 5 | { 6 | "username": "tutorials", 7 | "display_name": "first name last", 8 | "links": { 9 | "self": { 10 | "href": "https://bitbucket.org/api/2.0/users/tutorials" 11 | }, 12 | "avatar": { 13 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 14 | } 15 | } 16 | }, 17 | { 18 | "username": "mmangier", 19 | "display_name": "mmangier", 20 | "links": { 21 | "self": { 22 | "href": "https://bitbucket.org/api/2.0/users/mmangier" 23 | }, 24 | "avatar": { 25 | "href": "https://secure.gravatar.com/avatar/16b32ae6fcb078db0a088ce2516f2a05?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Ff5a36db3429b%2Fimg%2Fdefault_avatar%2F32%2Fuser_blue.png&s=32" 26 | } 27 | } 28 | } 29 | ], 30 | "page": 1, 31 | "size": 19 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 hirakiuc 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | 4 | require "bundler/gem_tasks" 5 | 6 | desc 'cleanup rcov, doc directories' 7 | task :clean do 8 | rm_rf 'coverage' 9 | rm_rf 'doc' 10 | rm_rf 'measurement' 11 | end 12 | 13 | # https://github.com/sferik/twitter/blob/master/Rakefile 14 | require 'rspec/core/rake_task' 15 | RSpec::Core::RakeTask.new(:spec) 16 | task :test => :spec 17 | 18 | if Dir.exist?('./features') 19 | RSpec::Core::RakeTask.new(:features) do |t| 20 | t.pattern = './features/**/*_spec.rb' 21 | t.rspec_opts = '-I ./features' 22 | end 23 | end 24 | 25 | begin 26 | require 'rubocop/rake_task' 27 | RuboCop::RakeTask.new 28 | rescue LoadError 29 | task :rubocop do 30 | $stderr.puts 'Rubocop is disabled.' 31 | end 32 | end 33 | 34 | require 'yard' 35 | YARD::Rake::YardocTask.new 36 | 37 | require 'yardstick/rake/measurement' 38 | Yardstick::Rake::Measurement.new do |measurement| 39 | measurement.output = 'measurement/report.txt' 40 | end 41 | 42 | require 'yardstick/rake/verify' 43 | Yardstick::Rake::Verify.new do |verify| 44 | verify.threshold = 59.6 45 | end 46 | 47 | task :default => [:spec, :rubocop] 48 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/api_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module ApiHelper 7 | private 8 | 9 | def next_proc(method, options) 10 | lambda do |next_options| 11 | send(method, options.merge(next_options)) 12 | end 13 | end 14 | 15 | def urlencode(v, key) 16 | if v.blank? || (escaped = CGI.escape(v.to_s)).blank? 17 | msg = "Invalid #{key} parameter. (#{v})" 18 | raise ArgumentError, msg 19 | end 20 | 21 | escaped 22 | end 23 | 24 | def build_path(base_path, *components) 25 | components.reduce(base_path) do |path, component| 26 | part = if component.is_a?(Array) 27 | urlencode(*component) 28 | else 29 | component.to_s 30 | end 31 | "#{path}/#{part}" 32 | end 33 | rescue ArgumentError => e 34 | raise ArgumentError, "Failed to build request URL: #{e}" 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/tinybucket/api/base_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | class Parser 6 | attr_reader :type, :options 7 | 8 | def initialize(type, model_class) 9 | parser_class = 10 | case type 11 | when :collection then Tinybucket::Parser::CollectionParser 12 | when :object then Tinybucket::Parser::ObjectParser 13 | else throw "Unknown parser type: #{type}" 14 | end 15 | 16 | @type = parser_class 17 | @options = { model_class: model_class } 18 | end 19 | end 20 | 21 | class BaseApi 22 | include Tinybucket::Connection 23 | include Tinybucket::Request 24 | 25 | protected 26 | 27 | # 28 | # Get parser 29 | # 30 | # @param type [Symbol] :collection or :object 31 | # @param model_class [Tinybucket::Model::Base] SubClass of Tinybucket::Model::Base 32 | # @return [Hash] 33 | def get_parser(type, model_class) 34 | Tinybucket::Api::Parser.new(type, model_class) 35 | end 36 | 37 | private 38 | 39 | def logger 40 | Tinybucket.logger 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/tinybucket/model/project.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | class Project < Base 6 | acceptable_attributes \ 7 | :type, :description, :links, :uuid, :created_on, 8 | :key, :updated_on, :is_private, :name, :owner 9 | 10 | # Update this project 11 | # 12 | # @param _params [Hash] 13 | # @raise [NotImplementedError] to be implemented 14 | def update(_params) 15 | raise NotImplementedError 16 | end 17 | 18 | # Destroy this project 19 | # 20 | # @raise [NotImplementedError] to be implemented. 21 | def destroy 22 | raise NotImplementedError 23 | end 24 | 25 | # Get repositories 26 | # 27 | # @return [Tinybucket::Resource::Repos] 28 | def repos 29 | repos_resource 30 | end 31 | 32 | private 33 | 34 | def owner_name 35 | raise 'This project is not loaded yet.' if (owner.nil? || owner['username'].nil?) 36 | owner['username'] 37 | end 38 | 39 | def repos_resource 40 | Tinybucket::Resource::Repos.new(owner_name, q: %(project.key="#{key}")) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/fixtures/team.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "teamsinspace", 3 | "website": null, 4 | "display_name": "Teams In Space", 5 | "uuid": "{61fc5cf6-d054-47d2-b4a9-061ccf858379}", 6 | "links": { 7 | "self": { 8 | "href": "https://bitbucket.org/api/2.0/teams/teamsinspace" 9 | }, 10 | "repositories": { 11 | "href": "https://bitbucket.org/api/2.0/repositories/teamsinspace" 12 | }, 13 | "html": { 14 | "href": "https://bitbucket.org/teamsinspace" 15 | }, 16 | "followers": { 17 | "href": "https://bitbucket.org/api/2.0/teams/teamsinspace/followers" 18 | }, 19 | "avatar": { 20 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2014/Sep/24/teamsinspace-avatar-3731530358-7_avatar.png" 21 | }, 22 | "members": { 23 | "href": "https://bitbucket.org/api/2.0/teams/teamsinspace/members" 24 | }, 25 | "following": { 26 | "href": "https://bitbucket.org/api/2.0/teams/teamsinspace/following" 27 | } 28 | }, 29 | "created_on": "2014-04-08T00:00:14.070969+00:00", 30 | "location": null, 31 | "type": "team" 32 | } 33 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/diff/1/get.json: -------------------------------------------------------------------------------- 1 | diff --git a/index.html b/index.html 2 | --- a/index.html 3 | +++ b/index.html 4 | @@ -82,7 +82,7 @@ 5 | 6 | 7 |

“Here's to the crazy ones, the misfits, the rebels, the troublemakers, the round pegs in the square holes... the ones who see things differently -- they're not fond of rules... You can quote them, disagree with them, glorify or vilify them, but the only thing you can't do is ignore them because they change things... they push the human race forward, and while some may see them as the crazy ones, we see genius, because the ones who are crazy enough to think that they can change the world, are the ones who do.”

8 | - Steve Jobs, Founder Of Apple Inc. 9 | + Steve Jobs, Founder of Apple Inc. 10 |
11 | 12 | 13 | @@ -122,7 +122,7 @@ 14 | 15 | 16 |

“No.”

17 | - Steve Jobs, Founder Of Apple Inc. 18 | + Steve Jobs, Founder of Apple Inc. 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/tinybucket/enumerator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | class Enumerator < ::Enumerator 5 | # Constructor 6 | # 7 | # This method create a enumerator to enumerate each items of iterator. 8 | # 9 | # @note This method return Lazy Enumerator if run on ruby 2.0.0 later. 10 | # 11 | # @param iterator [Tinybucket::Iterator] iterator instance. 12 | # @param block [Proc] a proc object to handle each item. 13 | def initialize(iterator, block) 14 | @iterator = iterator 15 | @block = block 16 | 17 | super() do |y| 18 | loop do 19 | v = @iterator.next 20 | m = @block ? @block.call(v) : v 21 | y.yield(m) 22 | end 23 | end 24 | 25 | lazy if lazy_enumerable? 26 | end 27 | 28 | # Get collection size. 29 | # 30 | # @see Tinybucket::Iterator#size 31 | # 32 | # @return [Fixnum, NillClass] collection size. 33 | def size 34 | @iterator.size 35 | end 36 | 37 | private 38 | 39 | def lazy_enumerable? 40 | ruby_major_version >= 2 41 | end 42 | 43 | def ruby_major_version 44 | RUBY_VERSION.split('.')[0].to_i 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/fixtures/teams/test_team/followers/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 10, 3 | "values": [ 4 | { 5 | "username": "tutorials", 6 | "kind": "team", 7 | "website": "https://tutorials.bitbucket.org/", 8 | "display_name": "first name last", 9 | "links": { 10 | "self": { 11 | "href": "https://api.bitbucket.org/2.0/users/tutorials" 12 | }, 13 | "repositories": { 14 | "href": "https://api.bitbucket.org/2.0/users/tutorials/repositories" 15 | }, 16 | "html": { 17 | "href": "https://api.bitbucket.org/tutorials" 18 | }, 19 | "followers": { 20 | "href": "https://api.bitbucket.org/2.0/users/tutorials/followers" 21 | }, 22 | "avatar": { 23 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 24 | }, 25 | "following": { 26 | "href": "https://api.bitbucket.org/2.0/users/tutorials/following" 27 | } 28 | }, 29 | "created_on": "2011-12-20T16:34:07.132459+00:00", 30 | "location": "Santa Monica, CA", 31 | "type": "team" 32 | } 33 | ], 34 | "page": 1, 35 | "size": 1 36 | } 37 | -------------------------------------------------------------------------------- /spec/fixtures/teams/test_team/members/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 50, 3 | "values": [ 4 | { 5 | "username": "tutorials", 6 | "kind": "team", 7 | "website": "https://tutorials.bitbucket.org/", 8 | "display_name": "first name last", 9 | "links": { 10 | "self": { 11 | "href": "https://api.bitbucket.org/2.0/users/tutorials" 12 | }, 13 | "repositories": { 14 | "href": "https://api.bitbucket.org/2.0/users/tutorials/repositories" 15 | }, 16 | "html": { 17 | "href": "https://api.bitbucket.org/tutorials" 18 | }, 19 | "followers": { 20 | "href": "https://api.bitbucket.org/2.0/users/tutorials/followers" 21 | }, 22 | "avatar": { 23 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Jul/17/tutorials-avatar-1826704565-4_avatar.png" 24 | }, 25 | "following": { 26 | "href": "https://api.bitbucket.org/2.0/users/tutorials/following" 27 | } 28 | }, 29 | "created_on": "2011-12-20T16:34:07.132459+00:00", 30 | "location": "Santa Monica, CA", 31 | "type": "team" 32 | } 33 | ], 34 | "page": 1, 35 | "size": 1 36 | } 37 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/pull_request/commits_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::PullRequest::Commits do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:pullrequest_id) { '1' } 9 | let(:pull_request) do 10 | Tinybucket::Model::PullRequest.new({}).tap do |m| 11 | m.id = pullrequest_id 12 | m.repo_owner = owner 13 | m.repo_slug = slug 14 | end 15 | end 16 | 17 | let(:options) { {} } 18 | let(:resource) do 19 | Tinybucket::Resource::PullRequest::Commits.new(pull_request, options) 20 | end 21 | 22 | describe 'Enumerable Methods' do 23 | let(:request_path) do 24 | "/repositories/#{owner}/#{slug}/pullrequests/#{pullrequest_id}/commits" 25 | end 26 | before { stub_enum_response(:get, request_path) } 27 | 28 | describe '#take(1)' do 29 | subject { resource.take(1) } 30 | it { expect(subject).to be_an_instance_of(Array) } 31 | end 32 | 33 | describe '#each' do 34 | it 'iterate models' do 35 | resource.each do |m| 36 | expect(m).to be_an_instance_of(Tinybucket::Model::Commit) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/pull_request/comments_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::PullRequest::Comments do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:pullrequest_id) { '1' } 9 | let(:pull_request) do 10 | Tinybucket::Model::PullRequest.new({}).tap do |m| 11 | m.id = pullrequest_id 12 | m.repo_owner = owner 13 | m.repo_slug = slug 14 | end 15 | end 16 | 17 | let(:options) { {} } 18 | let(:resource) do 19 | Tinybucket::Resource::PullRequest::Comments.new(pull_request, options) 20 | end 21 | 22 | describe 'Enumerable Methods' do 23 | let(:request_path) do 24 | "/repositories/#{owner}/#{slug}/pullrequests/#{pullrequest_id}/comments" 25 | end 26 | before { stub_enum_response(:get, request_path) } 27 | 28 | describe '#take(1)' do 29 | subject { resource.take(1) } 30 | it { expect(subject).to be_an_instance_of(Array) } 31 | end 32 | 33 | describe '#each' do 34 | it 'iterate models' do 35 | resource.each do |m| 36 | expect(m).to be_an_instance_of(Tinybucket::Model::Comment) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/tinybucket/model/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | class Base 6 | include ::ActiveModel::Serializers::JSON 7 | include Concerns::AcceptableAttributes 8 | include Concerns::Enumerable 9 | include Concerns::Reloadable 10 | include Concerns::ApiCallable 11 | 12 | def self.concern_included?(concern_name) 13 | mod_name = "Tinybucket::Model::Concerns::#{concern_name}".constantize 14 | ancestors.include?(mod_name) 15 | end 16 | 17 | def initialize(json) 18 | self.attributes = json 19 | @_loaded = !json.empty? 20 | end 21 | 22 | def attributes=(hash) 23 | hash.each_pair do |key, value| 24 | if acceptable_attribute?(key) 25 | send("#{key}=".intern, value) 26 | else 27 | logger.warn("Ignored '#{key}' attribute (value: #{value}). [#{self.class}]") 28 | end 29 | end 30 | end 31 | 32 | def attributes 33 | acceptable_attributes.map do |key| 34 | { key => send(key.intern) } 35 | end.reduce(&:merge) 36 | end 37 | 38 | protected 39 | 40 | def logger 41 | Tinybucket.logger 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/projects.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Projects < Base 6 | attr_accessor :owner 7 | 8 | # Initialize 9 | # 10 | # @param owner_name [String] 11 | # @param options [Hash] 12 | def initialize(owner_name, options = {}) 13 | @owner = owner_name 14 | @args = [options] 15 | end 16 | 17 | # Find the project 18 | # 19 | # @param project_key [String] 20 | # @param options [Hash] 21 | # @return [Tinybucket::Model::Project] 22 | def find(project_key, options = {}) 23 | projects_api.find(project_key, options) 24 | end 25 | 26 | # Create a new project 27 | # 28 | # NOTE: Not Implemented yet. 29 | # 30 | # @param _params [Hash] 31 | # @raise [NotImplementedError] to be implemented 32 | def create(_params) 33 | raise NotImplementedError 34 | end 35 | 36 | private 37 | 38 | def projects_api 39 | create_api('Projects').tap do |api| 40 | api.owner = @owner 41 | end 42 | end 43 | 44 | def enumerator 45 | create_enumerator(projects_api, :list, *@args) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/null_logger_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::NullLogger do 4 | subject { Tinybucket::NullLogger.new } 5 | 6 | describe '#level' do 7 | it { expect(subject).to respond_to(:level) } 8 | end 9 | 10 | describe '#level=' do 11 | it { expect(subject).to respond_to(:level=) } 12 | end 13 | 14 | describe '#fatal' do 15 | it { expect(subject).to respond_to(:fatal) } 16 | end 17 | 18 | describe '#fatal?' do 19 | it { expect(subject.fatal?).to be_falsey } 20 | end 21 | 22 | describe '#error' do 23 | it { expect(subject).to respond_to(:error) } 24 | end 25 | 26 | describe '#error?' do 27 | it { expect(subject.error?).to be_falsey } 28 | end 29 | 30 | describe '#warn' do 31 | it { expect(subject).to respond_to(:warn) } 32 | end 33 | 34 | describe '#warn?' do 35 | it { expect(subject.warn?).to be_falsey } 36 | end 37 | 38 | describe '#info' do 39 | it { expect(subject).to respond_to(:info) } 40 | end 41 | 42 | describe '#info?' do 43 | it { expect(subject.info?).to be_falsey } 44 | end 45 | 46 | describe '#debug' do 47 | it { expect(subject).to respond_to(:debug) } 48 | end 49 | 50 | describe '#debug?' do 51 | it { expect(subject.debug?).to be_falsey } 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/tinybucket/model/concerns/repository_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | module Concerns 6 | module RepositoryKeys 7 | extend ActiveSupport::Concern 8 | 9 | included do 10 | attr_accessor :repo_owner, :repo_slug 11 | 12 | def repo_keys? 13 | repo_owner.present? && repo_slug.present? 14 | end 15 | 16 | def repo_keys 17 | { repo_owner: repo_owner, repo_slug: repo_slug } 18 | end 19 | 20 | def repo_keys=(keys) 21 | self.repo_owner = keys[:repo_owner] 22 | self.repo_slug = keys[:repo_slug] 23 | end 24 | 25 | private 26 | 27 | def inject_repo_keys(result) 28 | case result 29 | when Tinybucket::Model::Page 30 | result.items.map do |m| 31 | next unless m.class.concern_included?(:RepositoryKeys) 32 | 33 | m.repo_keys = repo_keys 34 | end 35 | when Tinybucket::Model::Base 36 | result.repo_keys = repo_keys \ 37 | if result.class.concern_included?(:RepositoryKeys) 38 | end 39 | 40 | result 41 | end 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/commits_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module CommitsHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | build_path(base_path, 'commits') 13 | end 14 | 15 | def path_to_find(revision) 16 | build_path(base_path, 17 | 'commit', 18 | [revision, 'revision']) 19 | end 20 | 21 | def path_to_branch(branch) 22 | build_path(base_path, 23 | 'commits', 24 | [branch, 'branch']) 25 | end 26 | 27 | def path_to_tag(tag) 28 | build_path(base_path, 29 | 'commits', 30 | [tag, 'tag']) 31 | end 32 | 33 | 34 | def path_to_approve(revision) 35 | build_path(base_path, 36 | 'commit', 37 | [revision, 'revision'], 38 | 'approve') 39 | end 40 | 41 | def base_path 42 | build_path('/repositories', 43 | [repo_owner, 'repo_owner'], 44 | [repo_slug, 'repo_slug']) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/fixtures/teams/test_team/projects/myprj/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "{45234c3d-efa0-4c2a-be4e-78c1b30a30d0}", 3 | "links": { 4 | "self": { 5 | "href": "https://api.bitbucket.org/2.0/teams/test_team/projects/myprj" 6 | }, 7 | "html": { 8 | "href": "https://bitbucket.org/account/user/test_team/projects/myprj" 9 | }, 10 | "repositories": { 11 | "href": "https://api.bitbucket.org/2.0/repositories/test_team?q='project.key=\"myprj\"'" 12 | }, 13 | "avatar": { 14 | "href": "https://bitbucket.org/account/user/test_team/projects/myprj/avatar/32" 15 | } 16 | }, 17 | "description": null, 18 | "created_on": "2016-03-19T03:34:27.792230+00:00", 19 | "key": "myprj", 20 | "owner": { 21 | "username": "test_team", 22 | "display_name": "test team", 23 | "type": "team", 24 | "uuid": "{e0f17982-efab-43cb-8589-7bacabb37cab}", 25 | "links": { 26 | "self": { 27 | "href": "https://api.bitbucket.org/2.0/teams/test_team" 28 | }, 29 | "html": { 30 | "href": "https://bitbucket.org/test_team/" 31 | }, 32 | "avatar": { 33 | "href": "https://bitbucket.org/account/test_team/avatar/32/" 34 | } 35 | } 36 | }, 37 | "updated_on": "2016-03-19T03:34:27.792256+00:00", 38 | "type": "project", 39 | "is_private": false, 40 | "name": "myprj" 41 | } 42 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/projects_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Projects do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_team' } 7 | let(:options) { {} } 8 | let(:resource) { Tinybucket::Resource::Projects.new(owner, options) } 9 | 10 | describe '#find' do 11 | let(:project_key) { 'myprj' } 12 | let(:request_path) { "/teams/#{owner}/projects/#{project_key}" } 13 | before { stub_apiresponse(:get, request_path) } 14 | 15 | subject { resource.find(project_key) } 16 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Project) } 17 | end 18 | 19 | describe '#create' do 20 | let(:params) { {} } 21 | subject { resource.create(params) } 22 | it { expect { subject }.to raise_error(NotImplementedError) } 23 | end 24 | 25 | describe 'Enumerable Methods' do 26 | let(:request_path) { "/teams/#{owner}/projects/" } 27 | before { stub_enum_response(:get, request_path) } 28 | 29 | describe '#take(1)' do 30 | subject { resource.take(1) } 31 | it { expect(subject).to be_an_instance_of(Array) } 32 | end 33 | 34 | describe '#each' do 35 | it 'iterate models' do 36 | resource.each do |m| 37 | expect(m).to be_an_instance_of(Tinybucket::Model::Project) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/tags_spec.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'spec_helper' 3 | 4 | RSpec.describe Tinybucket::Resource::Tags do 5 | include ApiResponseMacros 6 | 7 | let(:tag) { "3.8" } 8 | let(:owner) { 'test_owner' } 9 | let(:slug) { 'test_repo' } 10 | 11 | let(:repo) do 12 | Tinybucket::Model::Repository.new({}).tap do |m| 13 | m.repo_owner = owner 14 | m.repo_slug = slug 15 | end 16 | end 17 | 18 | let(:options) { {} } 19 | let(:resource) { Tinybucket::Resource::Tags.new(repo, options) } 20 | 21 | describe '#find' do 22 | let(:request_path) { "/repositories/#{owner}/#{slug}/refs/tags/#{tag}" } 23 | before { stub_apiresponse(:get, request_path) } 24 | subject { resource.find(tag) } 25 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Tag) } 26 | end 27 | 28 | describe 'Enumerable Methods' do 29 | let(:request_path) { "/repositories/#{owner}/#{slug}/refs/tags" } 30 | before { stub_enum_response(:get, request_path) } 31 | 32 | describe '#take(1)' do 33 | subject { resource.take(1) } 34 | it { expect(subject).to be_an_instance_of(Array) } 35 | end 36 | 37 | describe '#each' do 38 | it 'iterate models' do 39 | resource.each do |m| 40 | expect(m).to be_an_instance_of(Tinybucket::Model::Tag) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/build_status_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module BuildStatusHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | build_path('/repositories', 13 | [repo_owner, 'repo_owner'], 14 | [repo_slug, 'repo_slug'], 15 | 'commit', 16 | [revision, 'revision'], 17 | 'statuses') 18 | end 19 | 20 | def path_to_find(revision, key) 21 | build_path(base_path(revision), 22 | [key, 'key']) 23 | end 24 | 25 | def path_to_post(revision) 26 | base_path(revision) 27 | end 28 | 29 | def path_to_put(revision, key) 30 | build_path(base_path(revision), 31 | [key, 'key']) 32 | end 33 | 34 | def base_path(revision) 35 | build_path('/repositories', 36 | [repo_owner, 'repo_owner'], 37 | [repo_slug, 'repo_slug'], 38 | 'commit', 39 | [revision, 'revision'], 40 | 'statuses', 41 | 'build') 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/commits_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Commits do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:repo) do 9 | Tinybucket::Model::Repository.new({}).tap do |m| 10 | m.repo_owner = owner 11 | m.repo_slug = slug 12 | end 13 | end 14 | 15 | let(:options) { {} } 16 | let(:resource) { Tinybucket::Resource::Commits.new(repo, options) } 17 | 18 | describe '#find' do 19 | let(:revision) { '1' } 20 | let(:request_path) { "/repositories/#{owner}/#{slug}/commit/#{revision}" } 21 | before { stub_apiresponse(:get, request_path) } 22 | subject { resource.find(revision) } 23 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Commit) } 24 | end 25 | 26 | describe 'Enumerable Methods' do 27 | let(:request_path) { "/repositories/#{owner}/#{slug}/commits" } 28 | before { stub_enum_response(:get, request_path) } 29 | 30 | describe '#take(1)' do 31 | subject { resource.take(1) } 32 | it { expect(subject).to be_an_instance_of(Array) } 33 | end 34 | 35 | describe '#each' do 36 | it 'iterate models' do 37 | resource.each do |m| 38 | expect(m).to be_an_instance_of(Tinybucket::Model::Commit) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/fixtures/teams/test_team/following/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 10, 3 | "values": [ 4 | { 5 | "username": "bitbucket", 6 | "kind": "team", 7 | "website": "http://bitbucket.org/", 8 | "display_name": "Atlassian Bitbucket", 9 | "links": { 10 | "self": { 11 | "href": "https://api.bitbucket.org/2.0/teams/bitbucket" 12 | }, 13 | "repositories": { 14 | "href": "https://api.bitbucket.org/2.0/teams/bitbucket/repositories" 15 | }, 16 | "html": { 17 | "href": "https://api.bitbucket.org/bitbucket" 18 | }, 19 | "followers": { 20 | "href": "https://api.bitbucket.org/2.0/teams/bitbucket/followers" 21 | }, 22 | "avatar": { 23 | "href": "https://secure.gravatar.com/avatar/5833a1ae59138da47cd14a9ab6bd1a7f?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Fbc85d1577e04%2Fimg%2Fdefault_team_avatar%2F32%2Fteam_blue.png&s=32" 24 | }, 25 | "members": { 26 | "href": "https://api.bitbucket.org/2.0/teams/bitbucket/members" 27 | }, 28 | "following": { 29 | "href": "https://api.bitbucket.org/2.0/teams/bitbucket/following" 30 | } 31 | }, 32 | "created_on": "2010-03-23T08:07:49+00:00", 33 | "location": "", 34 | "type": "team" 35 | } 36 | ], 37 | "page": 1, 38 | "size": 1 39 | } 40 | -------------------------------------------------------------------------------- /lib/tinybucket/model/page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # Page 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/meta/pagination 8 | # Paging through object collections 9 | # 10 | # @!attribute [r] attrs 11 | # This attribute is a Hash object which contains 12 | # 'size', 'page', 'pagelen', 'next', 'previous' key/value pairs. 13 | # @return [Hash] 14 | # @!attribute [r] items 15 | # This attribute is a array of model instance created with 16 | # 'values' attribute in json. 17 | class Page 18 | attr_reader :attrs, :items 19 | 20 | # Initialize with json and Model class. 21 | # 22 | # @param json [Hash] 23 | # @param item_klass [Class] 24 | def initialize(json, item_klass) 25 | @attrs = parse_attrs(json) 26 | @items = parse_values(json, item_klass) 27 | end 28 | 29 | private 30 | 31 | def parse_attrs(json) 32 | %w(size page pagelen next previous).map do |attr| 33 | { attr.to_sym => json[attr] } 34 | end.reduce(&:merge) 35 | end 36 | 37 | def parse_values(json, item_klass) 38 | return [] if json['values'].nil? || !json['values'].is_a?(Array) 39 | 40 | json['values'].map { |hash| item_klass.new(hash) } 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/branches_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Branches do 4 | include ApiResponseMacros 5 | 6 | let(:branch) { "master" } 7 | let(:owner) { 'test_owner' } 8 | let(:slug) { 'test_repo' } 9 | 10 | let(:repo) do 11 | Tinybucket::Model::Repository.new({}).tap do |m| 12 | m.repo_owner = owner 13 | m.repo_slug = slug 14 | end 15 | end 16 | 17 | let(:options) { {} } 18 | let(:resource) { Tinybucket::Resource::Branches.new(repo, options) } 19 | 20 | describe '#find' do 21 | let(:request_path) { "/repositories/#{owner}/#{slug}/refs/branches/#{branch}" } 22 | before { stub_apiresponse(:get, request_path) } 23 | subject { resource.find(branch) } 24 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Branch) } 25 | end 26 | 27 | describe 'Enumerable Methods' do 28 | let(:request_path) { "/repositories/#{owner}/#{slug}/refs/branches" } 29 | before { stub_enum_response(:get, request_path) } 30 | 31 | describe '#take(1)' do 32 | subject { resource.take(1) } 33 | it { expect(subject).to be_an_instance_of(Array) } 34 | end 35 | 36 | describe '#each' do 37 | it 'iterate models' do 38 | resource.each do |m| 39 | expect(m).to be_an_instance_of(Tinybucket::Model::Branch) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/tinybucket/model/tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # Tag 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/refs/tags 8 | # Tag Resource 9 | # 10 | # @!attribute [rw] links 11 | # @return [Hash] 12 | # @!attribute [rw] type 13 | # @return [String] 14 | # @!attribute [rw] name 15 | # @return [String] 16 | # @!attribute [rw] repository 17 | # @return [Hash] 18 | # @!attribute [rw] target 19 | # @return [Hash] 20 | class Tag < Base 21 | include Tinybucket::Model::Concerns::RepositoryKeys 22 | 23 | acceptable_attributes :links, :type, :name, :repository, :target, :tagger, :date, :message 24 | 25 | # Returns the commits available for the specific tag 26 | # 27 | # @param options [Hash] 28 | # @return [Tinybucket::Resource::Commits] 29 | def commits(options = {}) 30 | commits_resource.tag(name, options) 31 | end 32 | 33 | private 34 | 35 | def commits_resource(options = {}) 36 | Tinybucket::Resource::Commits.new(self, options) 37 | end 38 | 39 | def tags_api 40 | create_api('Tags', repo_keys) 41 | end 42 | 43 | def load_model 44 | tags_api.find(name, {}) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/lib/tinybucket_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket do 4 | 5 | describe 'new' do 6 | subject { Tinybucket.new } 7 | it { expect(subject).to be_instance_of(Tinybucket::Client) } 8 | end 9 | 10 | describe 'config' do 11 | subject { Tinybucket.config } 12 | it { expect(subject).to be_an_instance_of(Tinybucket::Config) } 13 | end 14 | 15 | describe 'configure' do 16 | subject(:config) { Tinybucket.config } 17 | let(:logger) { Logger.new($stdout) } 18 | let(:oauth_token) { 'test_oauth_token' } 19 | let(:oauth_secret) { 'test_oauth_secret' } 20 | let(:cache_store_options) { { logger: Tinybucket.logger } } 21 | 22 | it 'is configurable' do 23 | expect(config.logger).to be_nil 24 | 25 | expect do 26 | Tinybucket.configure do |config| 27 | config.logger = logger 28 | config.cache_store_options = cache_store_options 29 | config.oauth_token = oauth_token 30 | config.oauth_secret = oauth_secret 31 | end 32 | end.not_to raise_error 33 | 34 | expect(config.logger).to eq(logger) 35 | expect(config.cache_store_options).to eq(cache_store_options) 36 | expect(config.oauth_token).to eq(oauth_token) 37 | expect(config.oauth_secret).to eq(oauth_secret) 38 | end 39 | 40 | after { Tinybucket.instance_variable_set(:@config, nil) } 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/branch_restrictions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class BranchRestrictions < Base 6 | # Constructor 7 | # 8 | # @param repo [Tinybucket::Model::Repository] 9 | # @param options [Hash] 10 | def initialize(repo, options) 11 | @repo = repo 12 | @args = [options] 13 | end 14 | 15 | # Create new BranchRestriction on the repository. 16 | # 17 | # @param _options [Hash] 18 | # @return [Tinybucket::Model::BranchRestriction] 19 | def create(_options) 20 | raise NotImplementedError 21 | end 22 | 23 | # Find the BranchRestriction on the repository. 24 | # 25 | # @param restriction_id [String] 26 | # @param options [Hash] 27 | # @return [Tinybucket::Model::BranchRestriction] 28 | def find(restriction_id, options = {}) 29 | restrictions_api.find(restriction_id, options).tap do |m| 30 | inject_repo_keys(m, @repo.repo_keys) 31 | end 32 | end 33 | 34 | private 35 | 36 | def restrictions_api 37 | create_api('BranchRestrictions', @repo.repo_keys) 38 | end 39 | 40 | def enumerator 41 | create_enumerator(restrictions_api, :list, *@args) do |m| 42 | inject_repo_keys(m, @repo.repo_keys) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/tinybucket/model/branch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # Branch 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/refs/branches 8 | # Branch Resource 9 | # 10 | # @!attribute [rw] links 11 | # @return [Hash] 12 | # @!attribute [rw] type 13 | # @return [String] 14 | # @!attribute [rw] name 15 | # @return [String] 16 | # @!attribute [rw] repository 17 | # @return [Hash] 18 | # @!attribute [rw] target 19 | # @return [Hash] 20 | class Branch < Base 21 | include Tinybucket::Model::Concerns::RepositoryKeys 22 | 23 | acceptable_attributes :links, :type, :name, :repository, :target 24 | 25 | # Returns the commits available for the specific branch 26 | # 27 | # @param options [Hash] 28 | # @return [Tinybucket::Resource::Commits] 29 | def commits(options = {}) 30 | commits_resource.branch(name, options) 31 | end 32 | 33 | private 34 | 35 | def commits_resource(options = {}) 36 | Tinybucket::Resource::Commits.new(self, options) 37 | end 38 | 39 | def branches_api 40 | create_api('Branches', repo_keys) 41 | end 42 | 43 | def load_model 44 | branches_api.find(name, {}) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/pull_requests.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class PullRequests < Base 6 | def initialize(repo, options) 7 | @repo = repo 8 | @args = [options] 9 | end 10 | 11 | # Create a new pull request. 12 | # 13 | # @todo to be implemented. 14 | # @raise [NotImplementedError] to be implemented. 15 | def create(_options) 16 | raise NotImplementedError 17 | end 18 | 19 | # Get the specific pull request on the repository. 20 | # 21 | # @param pullrequest_id [String] 22 | # @param options [Hash] 23 | # @return [Tinybucket::Model::PullRequest] 24 | def find(pullrequest_id, options = {}) 25 | pull_requests_api.find(pullrequest_id, options).tap do |m| 26 | inject_repo_keys(m, @repo.repo_keys) 27 | end 28 | end 29 | 30 | # Get activities on the po 31 | # 32 | # TODO: To be implemented. 33 | def activities(_options) 34 | raise NotImplementedError 35 | end 36 | 37 | private 38 | 39 | def pull_requests_api 40 | create_api('PullRequests', @repo.repo_keys) 41 | end 42 | 43 | def enumerator 44 | create_enumerator(pull_requests_api, :list, *@args) do |m| 45 | inject_repo_keys(m, @repo.repo_keys) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/tag_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Tag do 4 | include ApiResponseMacros 5 | include ModelMacros 6 | 7 | let(:model_json) { load_json_fixture('tag') } 8 | 9 | let(:request_path) { nil } 10 | 11 | let(:owner) { 'test_owner' } 12 | let(:slug) { 'test_repo' } 13 | 14 | let(:model) do 15 | m = Tinybucket::Model::Tag.new(model_json) 16 | m.repo_owner = owner 17 | m.repo_slug = slug 18 | m 19 | end 20 | 21 | let(:request_method) { :get } 22 | let(:stub_options) { nil } 23 | 24 | before do 25 | if request_path 26 | opts = stub_options.present? ? stub_options : {} 27 | stub_apiresponse(request_method, request_path, opts) 28 | end 29 | end 30 | 31 | it_behaves_like 'model has acceptable_attributes', 32 | Tinybucket::Model::Tag, 33 | load_json_fixture('tag') 34 | 35 | describe 'model can reloadable' do 36 | let(:tag) { Tinybucket::Model::Tag.new({}) } 37 | before { @model = tag } 38 | it_behaves_like 'the model is reloadable' 39 | end 40 | 41 | describe 'commits' do 42 | let(:request_path) { "/repositories/#{owner}/#{slug}/commits/#{model.name}" } 43 | subject { model.commits } 44 | it { expect(subject).to be_an_instance_of(Tinybucket::Enumerator) } 45 | it { expect(subject).to all( be_an_instance_of Tinybucket::Model::Commit) } 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/fixtures/teams/get_role_admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 10, 3 | "values": [ 4 | { 5 | "username": "test_team", 6 | "website": null, 7 | "display_name": "test tesm", 8 | "uuid": "{e0f17982-effb-43cb-8589-7bacabb37cab}", 9 | "links": { 10 | "hooks": { 11 | "href": "https://api.bitbucket.org/2.0/teams/test_team/hooks" 12 | }, 13 | "self": { 14 | "href": "https://api.bitbucket.org/2.0/teams/test_team" 15 | }, 16 | "repositories": { 17 | "href": "https://api.bitbucket.org/2.0/repositories/test_team" 18 | }, 19 | "html": { 20 | "href": "https://bitbucket.org/test_team/" 21 | }, 22 | "followers": { 23 | "href": "https://api.bitbucket.org/2.0/teams/test_team/followers" 24 | }, 25 | "avatar": { 26 | "href": "https://bitbucket.org/account/test_team/avatar/32/" 27 | }, 28 | "members": { 29 | "href": "https://api.bitbucket.org/2.0/teams/test_team/members" 30 | }, 31 | "following": { 32 | "href": "https://api.bitbucket.org/2.0/teams/test_team/following" 33 | }, 34 | "projects": { 35 | "href": "https://api.bitbucket.org/2.0/teams/test_team/projects/" 36 | }, 37 | "snippets": { 38 | "href": "https://api.bitbucket.org/2.0/snippets/test_team" 39 | } 40 | }, 41 | "created_on": "2014-08-05T15:17:01.717714+00:00", 42 | "location": null, 43 | "type": "team" 44 | } 45 | ], 46 | "page": 1, 47 | "size": 1 48 | } 49 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/project_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Project do 4 | include ApiResponseMacros 5 | 6 | let(:model_json) { load_json_fixture('teams/test_team/projects/myprj/get') } 7 | let(:instance) { Tinybucket::Model::Project.new(model_json) } 8 | 9 | it_behaves_like 'model has acceptable_attributes', 10 | Tinybucket::Model::Project, 11 | load_json_fixture('teams/test_team/projects/myprj/get') 12 | 13 | describe '#update' do 14 | subject { instance.update({}) } 15 | it { expect { subject }.to raise_error(NotImplementedError) } 16 | end 17 | 18 | describe '#destroy' do 19 | subject { instance.destroy() } 20 | it { expect { subject }.to raise_error(NotImplementedError) } 21 | end 22 | 23 | describe '#repos' do 24 | subject { instance.repos } 25 | 26 | context 'with loaded instance' do 27 | let(:owner) { 'test_team' } 28 | let(:request_path) { "/repositories/#{owner}?q='project.key=myprj'" } 29 | before { stub_apiresponse(:get, request_path) } 30 | it { expect(subject).to be_an_instance_of(Tinybucket::Resource::Repos) } 31 | end 32 | 33 | context 'with the instance which does not have owner property' do 34 | let(:instance) { Tinybucket::Model::Project.new({}) } 35 | 36 | it { expect { subject }.to raise_error('This project is not loaded yet.') } 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/branch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Branch do 4 | include ApiResponseMacros 5 | include ModelMacros 6 | 7 | let(:model_json) { load_json_fixture('branch') } 8 | 9 | let(:request_path) { nil } 10 | 11 | let(:owner) { 'test_owner' } 12 | let(:slug) { 'test_repo' } 13 | 14 | let(:model) do 15 | m = Tinybucket::Model::Branch.new(model_json) 16 | m.repo_owner = owner 17 | m.repo_slug = slug 18 | m 19 | end 20 | 21 | let(:request_method) { :get } 22 | let(:stub_options) { nil } 23 | 24 | before do 25 | if request_path 26 | opts = stub_options.present? ? stub_options : {} 27 | stub_apiresponse(request_method, request_path, opts) 28 | end 29 | end 30 | 31 | it_behaves_like 'model has acceptable_attributes', 32 | Tinybucket::Model::Branch, 33 | load_json_fixture('branch') 34 | 35 | describe 'model can reloadable' do 36 | let(:branch) { Tinybucket::Model::Branch.new({}) } 37 | before { @model = branch } 38 | it_behaves_like 'the model is reloadable' 39 | end 40 | 41 | describe 'commits' do 42 | let(:request_path) { "/repositories/#{owner}/#{slug}/commits/#{model.name}" } 43 | subject { model.commits } 44 | it { expect(subject).to be_an_instance_of(Tinybucket::Enumerator) } 45 | it { expect(subject).to all( be_an_instance_of Tinybucket::Model::Commit) } 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/tinybucket/api/diff_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # Diff Api client 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/diff/%7Bspec%7D 8 | # diff Resource 9 | # 10 | # @!attribute [rw] repo_owner 11 | # @return [String] repository owner name. 12 | # @!attribute [rw] repo_slug 13 | # @return [String] {https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D repository slug}. 14 | class DiffApi < BaseApi 15 | include Tinybucket::Api::Helper::DiffHelper 16 | 17 | attr_accessor :repo_owner, :repo_slug 18 | 19 | # Send 'GET a diff' request 20 | # 21 | # @param spec [String] A specification such as a branch name, 22 | # revision, or commit SHA. 23 | # @param options [Hash] 24 | # @return [String] diff as raw text 25 | def find(spec, options = {}) 26 | get_path(path_to_find(spec), options) 27 | end 28 | 29 | # Send 'GET a patch' request 30 | # 31 | # @param spec [String] A specification such as a branch name, 32 | # revision, or commit SHA. 33 | # @param options [Hash] 34 | # @return [String] patch as raw text 35 | def find_patch(spec, options = {}) 36 | get_path(path_to_patch(spec), options) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/tinybucket/model/branch_restriction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # BranchRestriction 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/branch-restrictions 8 | # branch-restrictions Resource 9 | # 10 | # @!attribute [rw] groups 11 | # @return [Array] 12 | # @!attribute [rw] id 13 | # @return [Fixnum] 14 | # @!attribute [rw] kind 15 | # @return [String] 16 | # @!attribute [rw] links 17 | # @return [Hash] 18 | # @!attribute [rw] pattern 19 | # @return [String] 20 | # @!attribute [rw] users 21 | # @return [Array] 22 | # @!attribute [rw] uuid 23 | # @return [NillClass] 24 | class BranchRestriction < Base 25 | include Tinybucket::Model::Concerns::RepositoryKeys 26 | 27 | acceptable_attributes :groups, :id, :kind, :links, :pattern, :users, :uuid 28 | 29 | # Update this branch restriction 30 | # 31 | # @todo to be implemented 32 | # @raise [NotImplementedError] to be implemented. 33 | def update(_params) 34 | raise NotImplementedError 35 | end 36 | 37 | # Delete this branch restriction 38 | # 39 | # @todo to be implemented 40 | # @raise [NotImplementedError] to be implemented. 41 | def destroy 42 | raise NotImplementedError 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /tinybucket.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'tinybucket/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'tinybucket' 9 | spec.version = Tinybucket::VERSION 10 | spec.authors = ['hirakiuc'] 11 | spec.email = ['hirakiuc@gmail.com'] 12 | spec.summary = 'ruby wrapper for the Bitbucket REST API (v2) with oauth' 13 | spec.description = 'ruby wrapper for the Bitbucket REST API (v2) with oauth inspired by vongrippen/bitbucket.' 14 | spec.homepage = 'http://hirakiuc.github.io/tinybucket/' 15 | spec.license = 'MIT' 16 | 17 | spec.files = Dir['lib/**/*', 'LICENSE', 'Rakefile', 'Gemfile', 'tinybucket.gemspec', 'README.md', '.rubocop.yml'] 18 | spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) } 19 | spec.test_files = spec.files.grep(/^(test|spec|features)\//) 20 | spec.require_paths = ['lib'] 21 | 22 | spec.add_runtime_dependency 'activemodel', ['>= 4.1.6'] 23 | spec.add_runtime_dependency 'activesupport', ['>= 4.1.6'] 24 | spec.add_runtime_dependency 'faraday', ['~> 1.3'] 25 | spec.add_runtime_dependency 'faraday_middleware', ['~> 1.0'] 26 | spec.add_runtime_dependency 'faraday-http-cache', ['~> 2.2'] 27 | spec.add_runtime_dependency 'simple_oauth', ['~> 0.3'] 28 | 29 | spec.add_development_dependency 'bundler', '>= 2.2.33' 30 | end 31 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/commit/comments_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Commit::Comments do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:revision) { '1' } 9 | let(:commit) do 10 | Tinybucket::Model::Commit.new({}).tap do |m| 11 | m.hash = revision 12 | m.repo_owner = owner 13 | m.repo_slug = slug 14 | end 15 | end 16 | 17 | let(:options) { {} } 18 | let(:resource) { Tinybucket::Resource::Commit::Comments.new(commit, options) } 19 | 20 | describe '#find' do 21 | let(:comment_id) { '1' } 22 | let(:request_path) do 23 | "/repositories/#{owner}/#{slug}/commit/#{revision}/comments/#{comment_id}" 24 | end 25 | subject { resource.find(comment_id) } 26 | before { stub_apiresponse(:get, request_path) } 27 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Comment) } 28 | end 29 | 30 | describe 'Enumerable Methods' do 31 | let(:request_path) do 32 | "/repositories/#{owner}/#{slug}/commit/#{revision}/comments" 33 | end 34 | before { stub_enum_response(:get, request_path) } 35 | 36 | describe '#take(1)' do 37 | subject { resource.take(1) } 38 | it { expect(subject).to be_an_instance_of(Array) } 39 | end 40 | 41 | describe '#each' do 42 | it 'iterate models' do 43 | resource.each do |m| 44 | expect(m).to be_an_instance_of(Tinybucket::Model::Comment) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/comments_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module CommentsHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | base_path 13 | end 14 | 15 | def path_to_find(comment_id) 16 | build_path(base_path, 17 | [comment_id, 'comment_id']) 18 | end 19 | 20 | def base_path 21 | case commented_to 22 | when Tinybucket::Model::Commit 23 | base_path_of_commit 24 | when Tinybucket::Model::PullRequest 25 | base_path_of_pullrequest 26 | else 27 | raise ArgumentError, 'commented_to must be a pull_request or commit' 28 | end 29 | end 30 | 31 | def base_path_of_commit 32 | build_path('/repositories', 33 | [repo_owner, 'repo_owner'], 34 | [repo_slug, 'repo_slug'], 35 | 'commit', 36 | [commented_to.hash, 'revision'], 37 | 'comments') 38 | end 39 | 40 | def base_path_of_pullrequest 41 | build_path('/repositories', 42 | [repo_owner, 'repo_owner'], 43 | [repo_slug, 'repo_slug'], 44 | 'pullrequests', 45 | [commented_to.id, 'pull_request_id'], 46 | 'comments') 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/tinybucket/resource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | extend ActiveSupport::Autoload 6 | 7 | [ 8 | :Base, 9 | :Branches, 10 | :BranchRestrictions, 11 | :Commits, 12 | :Forks, 13 | :OwnersRepos, 14 | :Projects, 15 | :PublicRepos, 16 | :PullRequests, 17 | :Repos, 18 | :Teams, 19 | :Tags, 20 | :Watchers 21 | ].each do |klass_name| 22 | autoload klass_name 23 | end 24 | 25 | module Team 26 | extend ActiveSupport::Autoload 27 | 28 | [ 29 | :Base, 30 | :Followers, 31 | :Following, 32 | :Members, 33 | :Repos 34 | ].each do |klass_name| 35 | autoload klass_name 36 | end 37 | end 38 | 39 | module User 40 | extend ActiveSupport::Autoload 41 | 42 | [ 43 | :Base, 44 | :Followers, 45 | :Following, 46 | :Repos 47 | ].each do |klass_name| 48 | autoload klass_name 49 | end 50 | end 51 | 52 | module PullRequest 53 | extend ActiveSupport::Autoload 54 | 55 | [ 56 | :Base, 57 | :Commits, 58 | :Comments 59 | ].each do |klass_name| 60 | autoload klass_name 61 | end 62 | end 63 | 64 | module Commit 65 | extend ActiveSupport::Autoload 66 | 67 | [ 68 | :Base, 69 | :BuildStatuses, 70 | :Comments 71 | ].each do |klass_name| 72 | autoload klass_name 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/comments/1/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "self": { 4 | "href": "https://api.bitbucket.org/2.0/repositories/atlassian/atlassian-rest/commit/61d9e64348f9da407e62f64726337fd3bb24b466/comments/530189" 5 | }, 6 | "code": { 7 | "href": "https://api.bitbucket.org/2.0/repositories/atlassian/atlassian-rest/diff/61d9e64348f9da407e62f64726337fd3bb24b466?path=pom.xml" 8 | }, 9 | "html": { 10 | "href": "https://api.bitbucket.org/atlassian/atlassian-rest/commits/61d9e64348f9da407e62f64726337fd3bb24b466#comment-530189" 11 | } 12 | }, 13 | "content": { 14 | "raw": "Inline test comment.", 15 | "markup": "markdown", 16 | "html": "

Inline test comment.

" 17 | }, 18 | "created_on": "2013-11-07T23:55:24.486865+00:00", 19 | "user": { 20 | "username": "evzijst", 21 | "display_name": "Erik van Zijst", 22 | "links": { 23 | "self": { 24 | "href": "https://api.bitbucket.org/2.0/users/evzijst" 25 | }, 26 | "avatar": { 27 | "href": "https://secure.gravatar.com/avatar/f6bcbb4e3f665e74455bd8c0b4b3afba?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Fbf1e763db20f%2Fimg%2Fdefault_avatar%2F32%2Fuser_blue.png&s=32" 28 | } 29 | } 30 | }, 31 | "inline": { 32 | "to": null, 33 | "from": 381, 34 | "path": "pom.xml" 35 | }, 36 | "updated_on": "2013-11-07T23:55:24.502477+00:00", 37 | "id": 530189 38 | } 39 | -------------------------------------------------------------------------------- /lib/tinybucket/api/helper/pull_requests_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | module Helper 6 | module PullRequestsHelper 7 | include ::Tinybucket::Api::Helper::ApiHelper 8 | 9 | private 10 | 11 | def path_to_list 12 | base_path 13 | end 14 | 15 | def path_to_find(pr_id) 16 | build_path(base_path, 17 | [pr_id, 'pullrequest_id']) 18 | end 19 | 20 | def path_to_commits(pr_id) 21 | build_path(base_path, 22 | [pr_id, 'pullrequest_id'], 23 | 'commits') 24 | end 25 | 26 | def path_to_approve(pr_id) 27 | build_path(base_path, 28 | [pr_id, 'pullrequest_id'], 29 | 'approve') 30 | end 31 | 32 | def path_to_diff(pr_id) 33 | build_path(path_to_find(pr_id), 34 | 'diff') 35 | end 36 | 37 | def path_to_decline(pr_id) 38 | build_path(base_path, 39 | [pr_id, 'pullrequest_id'], 40 | 'decline') 41 | end 42 | 43 | def path_to_merge(pr_id) 44 | build_path(base_path, 45 | [pr_id, 'pullrequest_id'], 46 | 'merge') 47 | end 48 | 49 | def base_path 50 | build_path('/repositories', 51 | [repo_owner, 'repo_owner'], 52 | [repo_slug, 'repo_slug'], 53 | 'pullrequests') 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/profile_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Profile do 4 | include ApiResponseMacros 5 | include ModelMacros 6 | 7 | let(:model_json) { load_json_fixture('profile') } 8 | 9 | let(:request_path) { nil } 10 | 11 | let(:username) { 'test_owner' } 12 | 13 | let(:model) do 14 | m = Tinybucket::Model::Profile.new(model_json) 15 | m.username = username 16 | m 17 | end 18 | 19 | before { stub_apiresponse(:get, request_path) if request_path } 20 | 21 | it_behaves_like 'model has acceptable_attributes', 22 | Tinybucket::Model::Profile, 23 | load_json_fixture('profile') 24 | 25 | describe 'model can reloadable' do 26 | let(:profile) do 27 | m = Tinybucket::Model::Profile.new({}) 28 | m.username = username 29 | m 30 | end 31 | before { @model = profile } 32 | it_behaves_like 'the model is reloadable' 33 | end 34 | 35 | describe 'followers' do 36 | let(:request_path) { "/users/#{username}/followers" } 37 | subject { model.followers() } 38 | it { expect(subject).to be_an_instance_of(Tinybucket::Resource::User::Followers) } 39 | end 40 | 41 | describe 'following' do 42 | let(:request_path) { "/users/#{username}/following" } 43 | subject { model.following } 44 | it { expect(subject).to be_an_instance_of(Tinybucket::Resource::User::Following) } 45 | end 46 | 47 | describe 'repos' do 48 | let(:request_path) { "/repositories/#{username}" } 49 | subject { model.repos } 50 | it { expect(subject).to be_an_instance_of(Tinybucket::Resource::User::Repos) } 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/commit/build_statuses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | module Commit 6 | class BuildStatuses < Tinybucket::Resource::Commit::Base 7 | # Get the build status for the commit 8 | # 9 | # @param key [String] 10 | # @param options [Hash] 11 | # @option options [String] :state 12 | # @option options [String] :key 13 | # @option options [String] :name 14 | # @option options [String] :url 15 | # @option options [String] :description 16 | # @return [Tinybucket::Model::BuildStatus] 17 | def find(key, options = {}) 18 | build_status_api.find(@commit.hash, key, options).tap do |m| 19 | m.revision = @commit.hash 20 | m.repo_keys = @commit.repo_keys 21 | end 22 | end 23 | 24 | # Create a build status for the commit 25 | # 26 | # @param key [String] 27 | # @param options [Hash] 28 | # @return [Tinybucket::Model::BuildStatus] 29 | def create(key, options) 30 | build_status_api.post(@commit.hash, key, options).tap do |m| 31 | m.revision = @commit.hash 32 | m.repo_keys = @commit.repo_keys 33 | end 34 | end 35 | 36 | private 37 | 38 | def build_status_api 39 | create_api('BuildStatus', @commit.repo_keys).tap do |api| 40 | api.revision = @commit.hash 41 | end 42 | end 43 | 44 | def enumerator 45 | create_enumerator(build_status_api, :list, *@args) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/tinybucket/resource/commits.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Resource 5 | class Commits < Base 6 | def initialize(repo, options) 7 | @repo = repo 8 | @args = [options] 9 | end 10 | 11 | # Find the commit 12 | # 13 | # @param revision [String] 14 | # @param options [Hash] 15 | # @return [Tinybucket::Model::Commit] 16 | def find(revision, options = {}) 17 | commits_api.find(revision, options).tap do |m| 18 | inject_repo_keys(m, @repo.repo_keys) 19 | end 20 | end 21 | 22 | # Returns the commits for a specific branch 23 | # 24 | # @param name [String] 25 | # @param options [Hash] 26 | # @return [Tinybucket::Iterator] 27 | def branch(name, options = {}) 28 | create_enumerator(commits_api, :branch, name, options) do |m| 29 | inject_repo_keys(m, @repo.repo_keys) 30 | end 31 | end 32 | 33 | # Returns the commits for a specific tag 34 | # 35 | # @param name [String] 36 | # @param options [Hash] 37 | # @return [Tinybucket::Iterator] 38 | def tag(name, options = {}) 39 | create_enumerator(commits_api, :tag, name, options) do |m| 40 | inject_repo_keys(m, @repo.repo_keys) 41 | end 42 | end 43 | 44 | private 45 | 46 | def commits_api 47 | create_api('Commits', @repo.repo_keys) 48 | end 49 | 50 | def enumerator 51 | create_enumerator(commits_api, :list, *@args) do |m| 52 | inject_repo_keys(m, @repo.repo_keys) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.7 3 | Include: 4 | - 'lib/**/*.rb' 5 | Exclude: 6 | - '*.rb' 7 | - 'spec/**/*' 8 | - 'features/**/*' 9 | - 'test/**/*' 10 | - 'Gemfile' 11 | - 'Guardfile' 12 | - 'Rakefile' 13 | - '*.gemspec' 14 | 15 | Metrics/ClassLength: 16 | Max: 150 17 | 18 | MethodLength: 19 | Max: 30 20 | 21 | Documentation: 22 | Enabled: false 23 | 24 | AndOr: 25 | Enabled: false 26 | 27 | ParenthesesAroundCondition: 28 | Enabled: false 29 | 30 | Style/MethodCallWithoutArgsParentheses: 31 | Enabled: false 32 | 33 | # why use this option ? 34 | #RedundantBegin: 35 | # Enabled: false 36 | 37 | NumericLiterals: 38 | Enabled: false 39 | 40 | CyclomaticComplexity: 41 | Max: 15 42 | 43 | Next: 44 | Enabled: false 45 | 46 | Layout/HashAlignment: 47 | Enabled: false 48 | 49 | Style/RaiseArgs: 50 | Enabled: false 51 | 52 | Style/RedundantFreeze: 53 | Enabled: false 54 | 55 | Metrics/AbcSize: 56 | Max: 18 57 | 58 | Metrics/LineLength: 59 | Max: 100 60 | IgnoredPatterns: [ 61 | '^\s*#', 62 | 'ruby wrapper for the Bitbucket REST API*' 63 | ] 64 | 65 | Style/SymbolArray: 66 | Enabled: false 67 | 68 | Style/FormatStringToken: 69 | Enabled: false 70 | 71 | Style/RescueStandardError: 72 | Enabled: false 73 | 74 | Style/PercentLiteralDelimiters: 75 | Enabled: false 76 | 77 | Layout/SpaceAroundOperators: 78 | Enabled: false 79 | 80 | Layout/ExtraSpacing: 81 | Enabled: false 82 | 83 | Style/SafeNavigation: 84 | Enabled: false 85 | 86 | Lint/DuplicateMethods: 87 | Enabled: false 88 | 89 | Naming/MethodParameterName: 90 | MinNameLength: 1 91 | AllowNamesEndingNumbers: false 92 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper.rb' 2 | 3 | RSpec.describe Tinybucket::Connection do 4 | 5 | class MockApi 6 | include Tinybucket::Connection 7 | 8 | def config(key) 9 | nil 10 | end 11 | end 12 | 13 | let(:mock_api){ MockApi.new } 14 | 15 | describe 'clear_cache' do 16 | pending 'TODO: this method is required ?' 17 | end 18 | 19 | describe 'caching?' do 20 | pending 'TODO: this method is required ?' 21 | end 22 | 23 | describe 'connection(options, parser)' do 24 | subject{ mock_api.connection } 25 | 26 | context 'when no params are given ' do 27 | it { expect(subject).to be_instance_of(Faraday::Connection) } 28 | end 29 | end 30 | 31 | describe 'configure_auth' do 32 | subject(:handlers) { mock_api.connection.builder.handlers } 33 | 34 | context 'with Tinybucket.config.access_token' do 35 | before do 36 | allow_any_instance_of(Tinybucket::Config).to \ 37 | receive(:access_token).and_return('test token') 38 | end 39 | 40 | it 'should use oauth2 middleware' do 41 | expect(handlers).to include(FaradayMiddleware::OAuth2) 42 | expect(handlers).not_to include(FaradayMiddleware::OAuth) 43 | end 44 | end 45 | 46 | context 'without Tinybucket.config.access_token' do 47 | before do 48 | allow_any_instance_of(Tinybucket::Config).to \ 49 | receive(:access_token).and_return(nil) 50 | end 51 | 52 | it 'should use oauth middleware' do 53 | expect(handlers).not_to include(FaradayMiddleware::OAuth2) 54 | expect(handlers).to include(FaradayMiddleware::OAuth) 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/tinybucket/api/tags_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # Tags Api client 6 | # 7 | # @!attribute [rw] repo_owner 8 | # @return [String] repository owner name. 9 | # @!attribute [rw] repo_slug 10 | # @return [String] {https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D repo_slug}. 11 | class TagsApi < BaseApi 12 | include Tinybucket::Api::Helper::TagsHelper 13 | 14 | attr_accessor :repo_owner, :repo_slug 15 | 16 | # Send 'GET a tags list for a repository' request 17 | # 18 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/refs/tags#get 19 | # GET a tags list for a repository 20 | # 21 | # @param options [Hash] 22 | # @return [Tinybucket::Model::Page] 23 | def list(options = {}) 24 | get_path( 25 | path_to_list, 26 | options, 27 | get_parser(:collection, Tinybucket::Model::Tag) 28 | ) 29 | end 30 | 31 | # Send 'GET an individual tag' request 32 | # 33 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/refs/tags/%7Bname%7D#get 34 | # GET an individual tag 35 | # 36 | # @param name [String] The tag name 37 | # @param options [Hash] 38 | # @return [Tinybucket::Model::Tag] 39 | def find(name, options = {}) 40 | get_path( 41 | path_to_find(name), 42 | options, 43 | get_parser(:object, Tinybucket::Model::Tag) 44 | ) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/tinybucket/model/build_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # Build Status 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/commit/%7Bnode%7D/statuses/build 8 | # statuses/build Resource 9 | # 10 | # @!attribute [rw] state 11 | # @return [String] 12 | # @!attribute [rw] type 13 | # @return [String] 14 | # @!attribute [rw] key 15 | # @return [String] 16 | # @!attribute [rw] name 17 | # @return [String] 18 | # @!attribute [rw] url 19 | # @return [String] 20 | # @!attribute [rw] description 21 | # @return [String] 22 | # @!attribute [rw] links 23 | # @return [Hash] 24 | class BuildStatus < Base 25 | include Tinybucket::Model::Concerns::RepositoryKeys 26 | include Tinybucket::Model::Concerns::Reloadable 27 | 28 | acceptable_attributes \ 29 | :state, :type, :key, :name, :url, :description, :links, \ 30 | :created_on, :updated_on 31 | 32 | attr_accessor :revision 33 | 34 | # Update build status 35 | # 36 | # @param options [Hash] 37 | # @option options [String] :state 38 | # @return [Tinybucket::Model::BuildStatus] 39 | def update(options) 40 | build_status_api.put(revision, key, options).tap do |m| 41 | m.repo_keys = repo_keys 42 | m.revision = revision 43 | end 44 | end 45 | 46 | private 47 | 48 | def build_status_api 49 | create_api('BuildStatus', repo_keys) 50 | end 51 | 52 | def load_model 53 | build_status_api.find(revision, key) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/commit/1/comments/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "values": [{ 3 | "links": { 4 | "self": { 5 | "href": "https://api.bitbucket.org/2.0/repositories/atlassian/atlassian-rest/commit/61d9e64348f9da407e62f64726337fd3bb24b466/comments/530189" 6 | }, 7 | "code": { 8 | "href": "https://api.bitbucket.org/2.0/repositories/atlassian/atlassian-rest/diff/61d9e64348f9da407e62f64726337fd3bb24b466?path=pom.xml" 9 | }, 10 | "html": { 11 | "href": "https://bitbucket.org/atlassian/atlassian-rest/commits/61d9e64348f9da407e62f64726337fd3bb24b466#comment-530189" 12 | } 13 | }, 14 | "content": { 15 | "raw": "Inline test comment.", 16 | "markup": "markdown", 17 | "html": "

Inline test comment.

" 18 | }, 19 | "created_on": "2013-11-07T23:55:24.486865+00:00", 20 | "user": { 21 | "username": "evzijst", 22 | "display_name": "Erik van Zijst", 23 | "links": { 24 | "self": { 25 | "href": "https://api.bitbucket.org/2.0/users/evzijst" 26 | }, 27 | "avatar": { 28 | "href": "https://secure.gravatar.com/avatar/f6bcbb4e3f665e74455bd8c0b4b3afba?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Fbf1e763db20f%2Fimg%2Fdefault_avatar%2F32%2Fuser_blue.png&s=32" 29 | } 30 | } 31 | }, 32 | "inline": { 33 | "to": null, 34 | "from": 381, 35 | "path": "pom.xml" 36 | }, 37 | "updated_on": "2013-11-07T23:55:24.502477+00:00", 38 | "id": 530189 39 | }] 40 | } 41 | -------------------------------------------------------------------------------- /lib/tinybucket/api/branches_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # Branches Api client 6 | # 7 | # @!attribute [rw] repo_owner 8 | # @return [String] repository owner name. 9 | # @!attribute [rw] repo_slug 10 | # @return [String] {https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D repo_slug}. 11 | class BranchesApi < BaseApi 12 | include Tinybucket::Api::Helper::BranchesHelper 13 | 14 | attr_accessor :repo_owner, :repo_slug 15 | 16 | # Send 'GET a branches list for a repository' request 17 | # 18 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/refs/branches#get 19 | # GET a branches list for a repository 20 | # 21 | # @param options [Hash] 22 | # @return [Tinybucket::Model::Page] 23 | def list(options = {}) 24 | get_path( 25 | path_to_list, 26 | options, 27 | get_parser(:collection, Tinybucket::Model::Branch) 28 | ) 29 | end 30 | 31 | # Send 'GET an individual branch' request 32 | # 33 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/refs/branches/%7Bname%7D#get 34 | # GET an individual branch 35 | # 36 | # @param name [String] The branch name 37 | # @param options [Hash] 38 | # @return [Tinybucket::Model::Branch] 39 | def find(name, options = {}) 40 | get_path( 41 | path_to_find(name), 42 | options, 43 | get_parser(:object, Tinybucket::Model::Branch) 44 | ) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/tinybucket/api/repo_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # Repo Api client 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories 8 | # repository Resource 9 | # 10 | # @!attribute [rw] repo_owner 11 | # @return [String] repository owner name. 12 | # @!attribute [rw] repo_slug 13 | # @return [String] {https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D repository slug}. 14 | class RepoApi < BaseApi 15 | include Tinybucket::Api::Helper::RepoHelper 16 | 17 | attr_accessor :repo_owner, :repo_slug 18 | 19 | # Send 'GET a repository' request 20 | # 21 | # @param options [Hash] 22 | # @return [Tinybucket::Model::Repository] 23 | def find(options = {}) 24 | get_path( 25 | path_to_find, 26 | options, 27 | get_parser(:object, Tinybucket::Model::Repository) 28 | ) 29 | end 30 | 31 | # Send 'GET a list of watchers' request 32 | # 33 | # @param options [Hash] 34 | # @return [Tinybucket::Model::Page] 35 | def watchers(options = {}) 36 | get_path( 37 | path_to_watchers, 38 | options, 39 | get_parser(:collection, Tinybucket::Model::Profile) 40 | ) 41 | end 42 | 43 | # Send 'GET a list of forks' request 44 | # 45 | # @param options [Hash] 46 | # @return [Tinybucket::Model::Page] 47 | def forks(options = {}) 48 | get_path( 49 | path_to_forks, 50 | options, 51 | get_parser(:collection, Tinybucket::Model::Repository) 52 | ) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/tinybucket/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Request 5 | protected 6 | 7 | def get_path(path, params = {}, parser = nil, options = {}) 8 | request(:get, path, params, parser, options) 9 | end 10 | 11 | def patch_path(path, params = {}, parser = nil, options = {}) 12 | request(:patch, path, params, parser, options) 13 | end 14 | 15 | def post_path(path, params = {}, parser = nil, options = {}) 16 | request(:post, path, params, parser, options) 17 | end 18 | 19 | def put_path(path, params = {}, parser = nil, options = {}) 20 | request(:put, path, params, parser, options) 21 | end 22 | 23 | def delete_path(path, params = {}, parser = nil, options = {}) 24 | request(:delete, path, params, parser, options) 25 | end 26 | 27 | private 28 | 29 | def request(method, path, params, parser, options) 30 | conn = connection(parser, options) 31 | 32 | path = (conn.path_prefix + path).gsub(%r{//}, '/') \ 33 | if conn.path_prefix != '/' 34 | 35 | response = conn.send(method) do |request| 36 | case method.intern 37 | when :get, :delete 38 | request.body = params.delete('data') if params.key?('data') 39 | request.url(path, params) 40 | when :post, :put, :patch 41 | request.path = path 42 | request.body = extract_data_from_params(params) unless params.empty? 43 | else 44 | raise ArgumentError, 'unknown http method: ' + method 45 | end 46 | end 47 | 48 | response.body 49 | end 50 | 51 | def extract_data_from_params(params) 52 | if params.key?('data') && !params['data'].nil? 53 | params['data'] 54 | else 55 | params 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/pull_requests_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::PullRequests do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:repo) do 9 | Tinybucket::Model::Repository.new({}).tap do |m| 10 | m.repo_owner = owner 11 | m.repo_slug = slug 12 | end 13 | end 14 | 15 | let(:options) { {} } 16 | let(:resource) { Tinybucket::Resource::PullRequests.new(repo, options) } 17 | 18 | describe '#create' do 19 | let(:params) { {} } 20 | subject { resource.create(params) } 21 | it { expect { subject }.to raise_error(NotImplementedError) } 22 | end 23 | 24 | describe '#find' do 25 | let(:pullrequest_id) { '1' } 26 | let(:request_path) do 27 | "/repositories/#{owner}/#{slug}/pullrequests/#{pullrequest_id}" 28 | end 29 | before { stub_apiresponse(:get, request_path) } 30 | subject { resource.find(pullrequest_id) } 31 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::PullRequest) } 32 | end 33 | 34 | describe '#activities' do 35 | let(:params) { {} } 36 | subject { resource.activities(params) } 37 | it { expect { subject }.to raise_error(NotImplementedError) } 38 | end 39 | 40 | describe 'Enumerable Methods' do 41 | let(:request_path) do 42 | "/repositories/#{owner}/#{slug}/pullrequests" 43 | end 44 | before { stub_enum_response(:get, request_path) } 45 | 46 | describe '#take(1)' do 47 | subject { resource.take(1) } 48 | it { expect(subject).to be_an_instance_of(Array) } 49 | end 50 | 51 | describe '#each' do 52 | it 'iterate models' do 53 | resource.each do |m| 54 | expect(m).to be_an_instance_of(Tinybucket::Model::PullRequest) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/branch_restrictions_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::BranchRestrictions do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:repo) do 9 | Tinybucket::Model::Repository.new({}).tap do |m| 10 | m.repo_owner = owner 11 | m.repo_slug = slug 12 | end 13 | end 14 | 15 | let(:options) { {} } 16 | let(:resource) { Tinybucket::Resource::BranchRestrictions.new(repo, options) } 17 | 18 | describe 'constructor' do 19 | subject { resource } 20 | it 'create new instance' do 21 | expect(subject).to be_an_instance_of( 22 | Tinybucket::Resource::BranchRestrictions) 23 | end 24 | end 25 | 26 | describe '#create' do 27 | subject { resource.create({}) } 28 | it { expect { subject.create }.to raise_error(NotImplementedError) } 29 | end 30 | 31 | describe '#find' do 32 | let(:restriction_id) { '1' } 33 | let(:request_path) do 34 | "/repositories/#{owner}/#{slug}/branch-restrictions/#{restriction_id}" 35 | end 36 | subject { resource.find(restriction_id, {}) } 37 | before { stub_apiresponse(:get, request_path, {}) } 38 | it 'return model' do 39 | expect(subject).to be_an_instance_of(Tinybucket::Model::BranchRestriction) 40 | end 41 | end 42 | 43 | describe 'Enumerable Methods' do 44 | let(:request_path) { "/repositories/#{owner}/#{slug}/branch-restrictions" } 45 | before { stub_enum_response(:get, request_path) } 46 | 47 | describe '#take(1)' do 48 | subject { resource.take(1) } 49 | it { expect(subject).to be_an_instance_of(Array) } 50 | end 51 | 52 | describe '#each' do 53 | it 'iterate models' do 54 | resource.each do |m| 55 | expect(m).to be_an_instance_of(Tinybucket::Model::BranchRestriction) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/tinybucket/api/user_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # User Api client 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/users/%7Busername%7D 8 | # users Endpoint 9 | # 10 | # @!attribute [rw] username 11 | # @return [String] 12 | class UserApi < BaseApi 13 | include Tinybucket::Api::Helper::UserHelper 14 | 15 | attr_accessor :username 16 | 17 | # Send 'GET the user profile' request 18 | # 19 | # @param options [Hash] 20 | # @return [Tinybucket::Model::Profile] 21 | def profile(options = {}) 22 | get_path( 23 | path_to_find, 24 | options, 25 | get_parser(:object, Tinybucket::Model::Profile) 26 | ) 27 | end 28 | 29 | # Send 'GET the list of followers' request 30 | # 31 | # @param options [Hash] 32 | # @return [Tinybucket::Model::Page] 33 | def followers(options = {}) 34 | get_path( 35 | path_to_followers, 36 | options, 37 | get_parser(:collection, Tinybucket::Model::Profile) 38 | ) 39 | end 40 | 41 | # Send 'GET a list of accounts the user is following' request 42 | # 43 | # @param options [Hash] 44 | # @return [Tinybucket::Model::Page] 45 | def following(options = {}) 46 | get_path( 47 | path_to_following, 48 | options, 49 | get_parser(:collection, Tinybucket::Model::Profile) 50 | ) 51 | end 52 | 53 | # Send 'GET the user's repositories' request 54 | # 55 | # @param options [Hash] 56 | # @return [Tinybucket::Model::Page] 57 | def repos(options = {}) 58 | get_path( 59 | path_to_repos, 60 | options, 61 | get_parser(:collection, Tinybucket::Model::Repository) 62 | ) 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/tags_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::TagsApi do 4 | include ApiResponseMacros 5 | 6 | let(:tag) { "3.8" } 7 | let(:request_path) { nil } 8 | let(:options) { {} } 9 | let(:owner) { 'test_owner' } 10 | let(:slug) { 'test_repo' } 11 | 12 | let(:api) do 13 | api = Tinybucket::Api::TagsApi.new 14 | api.repo_owner = owner 15 | api.repo_slug = slug 16 | api 17 | end 18 | 19 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 20 | 21 | let(:request_method) { :get } 22 | let(:stub_options) { nil } 23 | 24 | before do 25 | if request_path 26 | opts = stub_options.present? ? stub_options : {} 27 | stub_apiresponse(request_method, request_path, opts) 28 | end 29 | end 30 | 31 | describe 'list' do 32 | subject { api.list(options) } 33 | 34 | context 'without owner' do 35 | let(:owner) { nil } 36 | it { expect { subject }.to raise_error(ArgumentError) } 37 | end 38 | 39 | context 'without slug' do 40 | let(:slug) { nil } 41 | it { expect { subject }.to raise_error(ArgumentError) } 42 | end 43 | 44 | context 'with owner and slug' do 45 | let(:request_path) { "/repositories/#{owner}/#{slug}/refs/tags" } 46 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 47 | end 48 | end 49 | 50 | describe 'find' do 51 | subject { api.find(tag, options) } 52 | 53 | context 'without owner' do 54 | let(:owner) { nil } 55 | it { expect { subject }.to raise_error(ArgumentError) } 56 | end 57 | 58 | context 'without slug' do 59 | let(:slug) { nil } 60 | it { expect { subject }.to raise_error(ArgumentError) } 61 | end 62 | 63 | context 'with owner and slug' do 64 | let(:request_path) do 65 | "/repositories/#{owner}/#{slug}/refs/tags/#{tag}" 66 | end 67 | it { expect(subject).to be_instance_of(Tinybucket::Model::Tag) } 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/build_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::BuildStatus do 4 | include ApiResponseMacros 5 | include ModelMacros 6 | 7 | let(:model_json) { load_json_fixture('build_status') } 8 | 9 | let(:request_method) { :get } 10 | let(:request_path) { nil } 11 | let(:stub_options) { nil } 12 | 13 | let(:commit_hash) { '1' } 14 | let(:owner) { 'test_owner' } 15 | let(:slug) { 'test_repo' } 16 | let(:model) do 17 | Tinybucket::Model::BuildStatus.new(model_json).tap do |m| 18 | m.repo_owner = owner 19 | m.repo_slug = slug 20 | m.revision = commit_hash 21 | end 22 | end 23 | 24 | before do 25 | if request_path 26 | opts = stub_options.present? ? stub_options : {} 27 | stub_apiresponse(request_method, request_path, opts) 28 | end 29 | end 30 | 31 | it_behaves_like 'model has acceptable_attributes', 32 | Tinybucket::Model::BuildStatus, 33 | load_json_fixture('build_status') 34 | 35 | describe 'model can reloadable' do 36 | let(:build_status) do 37 | Tinybucket::Model::BuildStatus.new({}).tap do |m| 38 | m.repo_owner = owner 39 | m.repo_slug = slug 40 | m.revision = commit_hash 41 | end 42 | end 43 | 44 | before { @model = build_status } 45 | it_behaves_like 'the model is reloadable' 46 | end 47 | 48 | describe '#update' do 49 | let(:status_key) { 'test_status' } 50 | let(:request_path) do 51 | "/repositories/#{owner}/#{slug}/commit/1/statuses/build/#{status_key}" 52 | end 53 | let(:params) do 54 | { 55 | state: 'SUCCESSFUL', 56 | name: 'test_repo test #10', 57 | url: 'https://example.com/path/to/build/info', 58 | description: 'Changes by test_owner' 59 | } 60 | end 61 | let(:request_method) { :put } 62 | 63 | subject { model.update(params) } 64 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::BuildStatus) } 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/tinybucket/model/comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # Comment 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/commit/%7Bsha%7D/comments 8 | # Comment Resource 9 | # 10 | # @!attribute [rw] links 11 | # @return [Hash] 12 | # @!attribute [rw] id 13 | # @return [Fixnum] 14 | # @!attribute [rw] parent 15 | # @return [Hash] 16 | # @!attribute [rw] filename 17 | # @return [String] 18 | # @!attribute [rw] content 19 | # @return [Hash] 20 | # @!attribute [rw] user 21 | # @return [Hash] 22 | # @!attribute [rw] inline 23 | # @return [Hash] 24 | # @!attribute [rw] created_on 25 | # @return [String] 26 | # @!attribute [rw] updated_on 27 | # @return [String] 28 | # @!attribute [rw] uuid 29 | # @return [NillClass] 30 | class Comment < Base 31 | include Tinybucket::Model::Concerns::RepositoryKeys 32 | 33 | acceptable_attributes \ 34 | :links, :id, :parent, :filename, :content, :user, :inline, \ 35 | :created_on, :updated_on, :uuid 36 | 37 | # @!attribute [rw] commented_to 38 | # @return [Tinybucket::Model::PullRequest, Tinybucket::Model::Commit] 39 | attr_accessor :commented_to 40 | 41 | private 42 | 43 | def commit_api 44 | create_api('Comments', repo_keys) 45 | end 46 | 47 | def pull_request_api 48 | create_api('PullRequests', repo_keys) 49 | end 50 | 51 | def load_model 52 | api = 53 | case commented_to 54 | when Tinybucket::Model::Commit 55 | commit_api 56 | when Tinybucket::Model::PullRequest 57 | pull_request_api 58 | else 59 | raise ArgumentError, 'commented_to was invalid' 60 | end 61 | 62 | api.commented_to = commented_ato 63 | api.find(id, {}) 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/branches_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::BranchesApi do 4 | include ApiResponseMacros 5 | 6 | let(:branch) { "master" } 7 | let(:request_path) { nil } 8 | let(:options) { {} } 9 | let(:owner) { 'test_owner' } 10 | let(:slug) { 'test_repo' } 11 | 12 | let(:api) do 13 | api = Tinybucket::Api::BranchesApi.new 14 | api.repo_owner = owner 15 | api.repo_slug = slug 16 | api 17 | end 18 | 19 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 20 | 21 | let(:request_method) { :get } 22 | let(:stub_options) { nil } 23 | 24 | before do 25 | if request_path 26 | opts = stub_options.present? ? stub_options : {} 27 | stub_apiresponse(request_method, request_path, opts) 28 | end 29 | end 30 | 31 | describe 'list' do 32 | subject { api.list(options) } 33 | 34 | context 'without owner' do 35 | let(:owner) { nil } 36 | it { expect { subject }.to raise_error(ArgumentError) } 37 | end 38 | 39 | context 'without slug' do 40 | let(:slug) { nil } 41 | it { expect { subject }.to raise_error(ArgumentError) } 42 | end 43 | 44 | context 'with owner and slug' do 45 | let(:request_path) { "/repositories/#{owner}/#{slug}/refs/branches" } 46 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 47 | end 48 | end 49 | 50 | describe 'find' do 51 | subject { api.find(branch, options) } 52 | 53 | context 'without owner' do 54 | let(:owner) { nil } 55 | it { expect { subject }.to raise_error(ArgumentError) } 56 | end 57 | 58 | context 'without slug' do 59 | let(:slug) { nil } 60 | it { expect { subject }.to raise_error(ArgumentError) } 61 | end 62 | 63 | context 'with owner and slug' do 64 | let(:request_path) do 65 | "/repositories/#{owner}/#{slug}/refs/branches/#{branch}" 66 | end 67 | it { expect(subject).to be_instance_of(Tinybucket::Model::Branch) } 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/tinybucket.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'tinybucket/version' 4 | 5 | require 'active_support/dependencies/autoload' 6 | 7 | require 'active_support/core_ext/hash' 8 | require 'active_support/configurable' 9 | require 'active_support/inflector' 10 | 11 | require 'faraday' 12 | require 'faraday_middleware' 13 | require 'faraday_middleware/response_middleware' 14 | require 'faraday_middleware/follow_oauth_redirects' 15 | require 'faraday-http-cache' 16 | 17 | require 'active_model' 18 | 19 | require 'logger' 20 | require 'uri' 21 | 22 | require 'tinybucket/enumerator' 23 | require 'tinybucket/iterator' 24 | 25 | require 'tinybucket/config' 26 | require 'tinybucket/null_logger' 27 | require 'tinybucket/api' 28 | require 'tinybucket/api_factory' 29 | require 'tinybucket/api/helper' 30 | require 'tinybucket/connection' 31 | require 'tinybucket/constants' 32 | require 'tinybucket/error' 33 | require 'tinybucket/model/concerns' 34 | require 'tinybucket/model' 35 | require 'tinybucket/parser' 36 | require 'tinybucket/request' 37 | require 'tinybucket/resource' 38 | require 'tinybucket/response' 39 | 40 | require 'tinybucket/client' 41 | 42 | require 'active_support/notifications' 43 | ActiveSupport::Notifications.subscribe('request.faraday') \ 44 | do |_name, start_time, end_time, _, env| 45 | url = env[:url] 46 | http_method = env[:method].to_s.upcase 47 | duration = end_time - start_time 48 | Tinybucket.logger.debug \ 49 | format( 50 | '[%s] %s %s (%.3f s)', 51 | url.host, http_method, url.request_uri, duration 52 | ) 53 | end 54 | 55 | module Tinybucket 56 | class << self 57 | include ActiveSupport::Configurable 58 | attr_accessor :logger, :api_client 59 | 60 | def new 61 | @api_client = Tinybucket::Client.new 62 | end 63 | 64 | def configure 65 | yield(config) 66 | end 67 | 68 | def config 69 | @config ||= Tinybucket::Config.new 70 | end 71 | 72 | def logger 73 | config.logger || Tinybucket::NullLogger.new 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/pullrequests/1/comments/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 1, 3 | "next": "https://api.bitbucket.org/2.0/repositories/test_owner/test_repo/pullrequests/1/comments?pagelen=1&page=2", 4 | "values": [{ 5 | "parent": { 6 | "id": 25334, 7 | "links": { 8 | "self": { 9 | "href": "https://bitbucket.org/api/2.0/repositories/bitbucket/bitbucket/pullrequests/3767/comments/25334" 10 | } 11 | } 12 | }, 13 | "links": { 14 | "self": { 15 | "href": "https://bitbucket.org/api/2.0/repositories/bitbucket/bitbucket/pullrequests/3767/comments/25337" 16 | }, 17 | "html": { 18 | "href": "https://bitbucket.org/bitbucket/bitbucket/pull-request/3767/_/diff#comment-25337" 19 | } 20 | }, 21 | "content": { 22 | "raw": "If we are leaving the email attached to the team account, Gravatar should still work fine. The problem would be they can't change the Gravatar associated account then.", 23 | "markup": "markdown", 24 | "html": "

If we are leaving the email attached to the team account, Gravatar should still work fine. The problem would be they can't change the Gravatar associated account then.

" 25 | }, 26 | "created_on": "2013-11-07T17:11:20.986517+00:00", 27 | "user": { 28 | "username": "mfrauenholtz", 29 | "display_name": "Michael Frauenholtz", 30 | "links": { 31 | "self": { 32 | "href": "https://bitbucket.org/api/2.0/users/mfrauenholtz" 33 | }, 34 | "avatar": { 35 | "href": "https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Aug/24/mfrauenholtz-avatar-1858533797-5_avatar.png" 36 | } 37 | } 38 | }, 39 | "updated_on": "2013-11-07T17:11:20.989028+00:00", 40 | "id": 25337 41 | }], 42 | "page": 1, 43 | "size": 4 44 | } 45 | -------------------------------------------------------------------------------- /lib/tinybucket/api/branch_restrictions_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # BranchRestrictions API client 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/branch-restrictions 8 | # branch-restrictions Resource 9 | # 10 | # @!attribute [rw] repo_owner 11 | # @return [String] repository owner name. 12 | # @!attribute [rw] repo_slug 13 | # @return [String] {https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D repository slug}. 14 | class BranchRestrictionsApi < BaseApi 15 | include Tinybucket::Api::Helper::BranchRestrictionsHelper 16 | 17 | attr_accessor :repo_owner, :repo_slug 18 | 19 | # Send 'GET the branch-restrictions' request. 20 | # 21 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/branch-restrictions#get 22 | # GET the branch-restrictions 23 | # 24 | # @param options [Hash] 25 | # @return [Tinybucket::Model::Page] 26 | def list(options = {}) 27 | get_path( 28 | path_to_list, 29 | options, 30 | get_parser(:collection, Tinybucket::Model::BranchRestriction) 31 | ) 32 | end 33 | 34 | # Send 'GET a specific restriction' request. 35 | # 36 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Busername%7D/%7Brepo_slug%7D/branch-restrictions/%7Bid%7D#get 37 | # GET a specific restriction 38 | # 39 | # @param restriction_id [String] The restriction's identifier 40 | # @param options [Hash] 41 | # @return [Tinybucket::Model::BranchRestriction] 42 | def find(restriction_id, options = {}) 43 | get_path( 44 | path_to_find(restriction_id), 45 | options, 46 | get_parser(:object, Tinybucket::Model::BranchRestriction) 47 | ) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/tinybucket/model/profile.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Model 5 | # Profile 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/users/%7Busername%7D 8 | # users Endpoint 9 | # 10 | # @!attribute [rw] username 11 | # @return [String] 12 | # @!attribute [rw] kind 13 | # @return [NillClass] 14 | # @!attribute [rw] website 15 | # @return [String] 16 | # @!attribute [rw] display_name 17 | # @return [String] 18 | # @!attribute [rw] links 19 | # @return [Hash] 20 | # @!attribute [rw] created_on 21 | # @return [String] 22 | # @!attribute [rw] location 23 | # @return [String] 24 | # @!attribute [rw] type 25 | # @return [String] 26 | # @!attribute [rw] uuid 27 | # @return [String] 28 | class Profile < Base 29 | acceptable_attributes \ 30 | :username, :kind, :website, :display_name, 31 | :links, :created_on, :location, :type, :uuid 32 | 33 | # Get this user's followers 34 | # 35 | # @param options [Hash] 36 | # @return [Tinybucket::Resource::User::Followers] 37 | def followers(options = {}) 38 | Tinybucket::Resource::User::Followers.new(username, options) 39 | end 40 | 41 | # Get users which this user is following 42 | # 43 | # @param options [Hash] 44 | # @return [Tinybucket::Resource::User::Following] 45 | def following(options = {}) 46 | Tinybucket::Resource::User::Following.new(username, options) 47 | end 48 | 49 | # Get this user's repositories 50 | # 51 | # @param options [Hash] 52 | # @return [Tinybucket::Resource::User::Repos] 53 | def repos(options = {}) 54 | Tinybucket::Resource::User::Repos.new(username, options) 55 | end 56 | 57 | private 58 | 59 | def user_api 60 | create_api('User').tap do |api| 61 | api.username = username 62 | end 63 | end 64 | 65 | def load_model 66 | user_api.profile 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/faraday_middleware/follow_oauth_redirects.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'faraday' 4 | require 'faraday_middleware' 5 | 6 | module FaradayMiddleware 7 | class FollowOAuthRedirects < FollowRedirects 8 | dependency 'simple_oauth' 9 | 10 | AUTH_HEADER = OAuth::AUTH_HEADER 11 | CONTENT_TYPE = OAuth::CONTENT_TYPE 12 | TYPE_URLENCODED = OAuth::TYPE_URLENCODED 13 | 14 | def update_env(env, request_body, response) 15 | env = super(env, request_body, response) 16 | 17 | # update Authentication Header 18 | if oauth_signed_request?(env) 19 | env[:request_headers][OAuth::AUTH_HEADER] = oauth_header(env).to_s 20 | end 21 | 22 | env 23 | end 24 | 25 | def oauth_header(env) 26 | SimpleOAuth::Header.new env[:method], 27 | env[:url].to_s, 28 | signature_params(body_params(env)), 29 | oauth_options(env) 30 | end 31 | 32 | def oauth_signed_request?(env) 33 | env[:request].oauth 34 | end 35 | 36 | def oauth_options(env) 37 | extra = env[:request][:oauth] 38 | if extra.present? and extra.is_a? Hash and !extra.empty? 39 | @options.merge extra 40 | else 41 | @options 42 | end 43 | end 44 | 45 | def body_params(env) 46 | if include_body_params?(env) 47 | if env[:body].respond_to?(:to_str) 48 | ::Faraday::Utils.parse_nested_query env[:body] 49 | else 50 | env[:body] 51 | end 52 | end || {} 53 | end 54 | 55 | def include_body_params?(env) 56 | # see RFC 5849, section 3.4.1.3.1 for details 57 | !(type = env[:request_headers][OAuth::CONTENT_TYPE]) || 58 | type == OAuth::TYPE_URLENCODED 59 | end 60 | 61 | def signature_params(params) 62 | return params if params.empty? 63 | 64 | params.reject { |_k, v| v.respond_to?(:content_type) } 65 | end 66 | end 67 | 68 | if Faraday::Middleware.respond_to? :register_middleware 69 | Faraday::Response.register_middleware \ 70 | follow_oauth_redirects: -> { FollowOAuthRedirects } 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/tinybucket/iterator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | # Iterator 5 | # 6 | # This iterator iterate items by sending request ot Bitbucket Cloud REST API. 7 | class Iterator 8 | # Constructor 9 | # 10 | # @param api_client [Tinybucket::Api::Base] an instance of Api Client class. 11 | # @param method [Symbol] method name to invoke api_client method. 12 | # @param args [Array] arguments to pass method call of 13 | # api_client as arguments. 14 | def initialize(api_client, method, *args) 15 | @client = api_client 16 | @method = method 17 | @args = args 18 | 19 | @attrs = {} 20 | @values = [] 21 | @pos = nil 22 | end 23 | 24 | def next 25 | next_value 26 | end 27 | 28 | # Get collection size. 29 | # 30 | # @note This method return {https://developer.atlassian.com/bitbucket/api/2/reference/meta/pagination 31 | # size attribute of object collection wrapper}. 32 | # So this method may return nil. 33 | # 34 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/meta/pagination 35 | # Paging through object collections 36 | # 37 | # @return [Fixnum, NillClas] collection size. 38 | def size 39 | load_next if next? 40 | @attrs[:size] 41 | end 42 | 43 | private 44 | 45 | def next_value 46 | load_next if next? 47 | 48 | @values.fetch(@pos).tap { @pos += 1 } 49 | rescue IndexError 50 | raise StopIteration 51 | end 52 | 53 | def next? 54 | @pos.nil? || (@values.size == @pos && @attrs[:next]) 55 | end 56 | 57 | def load_next 58 | @pos = 0 if @pos.nil? 59 | 60 | page = @client.send(@method, *next_params) 61 | 62 | @attrs = page.attrs 63 | @values.concat(page.items) 64 | end 65 | 66 | def next_params 67 | params = 68 | if @attrs.empty? 69 | {} 70 | else 71 | query = URI.parse(@attrs[:next]).query 72 | Hash[*query.split(/&|=/).map { |v| CGI.unescape(v) }] 73 | end 74 | 75 | @args[-1].merge!(params) 76 | @args 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/user_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::UserApi do 4 | include ApiResponseMacros 5 | 6 | let(:api) do 7 | api = Tinybucket::Api::UserApi.new 8 | api.username = user 9 | api 10 | end 11 | 12 | let(:user) { 'test_owner' } 13 | let(:request_path) { nil } 14 | before { stub_apiresponse(:get, request_path) if request_path } 15 | 16 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 17 | 18 | describe 'profile' do 19 | subject { api.profile } 20 | 21 | context 'when without username' do 22 | let(:user) { nil } 23 | it { expect { subject }.to raise_error(ArgumentError) } 24 | end 25 | 26 | context 'when with username' do 27 | let(:request_path) { "/users/#{user}" } 28 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Profile) } 29 | end 30 | end 31 | 32 | describe 'followers' do 33 | subject { api.followers } 34 | 35 | context 'when without username' do 36 | let(:user) { nil } 37 | it { expect { subject }.to raise_error(ArgumentError) } 38 | end 39 | 40 | context 'when with username' do 41 | let(:request_path) { "/users/#{user}/followers" } 42 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 43 | end 44 | end 45 | 46 | describe 'following' do 47 | subject { api.following } 48 | 49 | context 'when without username' do 50 | let(:user) { nil } 51 | it { expect { subject }.to raise_error(ArgumentError) } 52 | end 53 | 54 | context 'when with username' do 55 | let(:request_path) { "/users/#{user}/following" } 56 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 57 | end 58 | end 59 | 60 | describe 'repos' do 61 | subject { api.repos } 62 | 63 | context 'when without username' do 64 | let(:user) { nil } 65 | it { expect { subject }.to raise_error(ArgumentError) } 66 | end 67 | 68 | context 'when with username' do 69 | let(:request_path) { "/repositories/#{user}" } 70 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/support/api_response_macros.rb: -------------------------------------------------------------------------------- 1 | module ApiResponseMacros 2 | def stub_apiresponse(method, path, options = {}) 3 | response_headers = { content_type: 'application/json' }.merge(options) 4 | 5 | ext = 6 | case response_headers[:content_type] 7 | when 'plain/text' then; 'txt' 8 | else 'json' 9 | end 10 | 11 | stub_request(method, api_path(path)) 12 | .to_return( 13 | status: (options[:status_code] || 200), 14 | body: (options[:message] || fixture_json(method, path, ext)), 15 | headers: response_headers) 16 | end 17 | 18 | def stub_enum_response(method, path) 19 | response_headers = { content_type: 'application/json' } 20 | 21 | first_page_json = fixture_json(method, path, 'json') 22 | 23 | # stub for first page 24 | stub_request(method, api_path(path)) 25 | .to_return( 26 | status: 200, 27 | body: first_page_json, 28 | headers: response_headers) 29 | 30 | # stub for second(last) page 31 | next_url = JSON.parse(first_page_json)['next'] || api_path(path, page: 2) 32 | stub_request(method, next_url) 33 | .to_return( 34 | status: 200, 35 | body: last_page_json(api_path(path, page: 1)), 36 | headers: response_headers) 37 | end 38 | 39 | private 40 | 41 | def api_path(path, params = {}) 42 | 'https://api.bitbucket.org/2.0' + path + query_string(params) 43 | end 44 | 45 | def query_string(params) 46 | return '' if params.empty? 47 | 48 | key_values = params.to_a.map do |v| 49 | [v[0].to_s, v[1].to_s] 50 | end 51 | 52 | '?' + URI.encode_www_form(key_values) 53 | end 54 | 55 | def fixture_json(method, path, ext) 56 | parts = path.split('?') 57 | 58 | path = 'spec/fixtures' + parts[0] 59 | fname = method.to_s 60 | fname += '_' + parts[1].gsub(/[\/??&=]/, '_').gsub('\'', '_') if parts[1].present? 61 | fname += '.' + ext 62 | 63 | File.read(path + '/' + fname) 64 | end 65 | 66 | def last_page_json(prev) 67 | JSON.generate( 68 | size: 1, 69 | page: 2, 70 | pagelen: 0, 71 | next: nil, 72 | previous: prev, 73 | values: [] 74 | ) 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/commit/build_statuses_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Commit::BuildStatuses do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:revision) { '1' } 9 | let(:commit) do 10 | Tinybucket::Model::Commit.new({}).tap do |m| 11 | m.hash = revision 12 | m.repo_owner = owner 13 | m.repo_slug = slug 14 | end 15 | end 16 | 17 | let(:options) { {} } 18 | let(:resource) do 19 | Tinybucket::Resource::Commit::BuildStatuses.new(commit, options) 20 | end 21 | 22 | let(:status_key) { 'test_status' } 23 | 24 | describe '#find' do 25 | let(:request_path) do 26 | "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses/build/#{status_key}" 27 | end 28 | subject { resource.find(status_key) } 29 | before { stub_apiresponse(:get, request_path) } 30 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::BuildStatus) } 31 | end 32 | 33 | describe '#create' do 34 | let(:request_path) do 35 | "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses/build" 36 | end 37 | let(:params) do 38 | { 39 | state: 'INPROGRESS', 40 | name: 'test_repo test #10', 41 | url: 'https://example.com/path/to/build/info', 42 | description: 'Changes by test_owner' 43 | } 44 | end 45 | 46 | before { stub_apiresponse(:post, request_path) } 47 | subject { resource.create(status_key, params) } 48 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::BuildStatus) } 49 | end 50 | 51 | describe 'Enumerable Methods' do 52 | let(:request_path) do 53 | "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses" 54 | end 55 | before { stub_enum_response(:get, request_path) } 56 | 57 | describe '#take(1)' do 58 | subject { resource.take(1) } 59 | it { expect(subject).to be_an_instance_of(Array) } 60 | end 61 | 62 | describe '#each' do 63 | it 'iterate models' do 64 | resource.each do |m| 65 | expect(m).to be_an_instance_of(Tinybucket::Model::BuildStatus) 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/tinybucket/api/build_status_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # BuildStatus Api client 6 | # 7 | # @see https://confluence.atlassian.com/bitbucket/statuses-build-resource-779295267.html 8 | # statuses/build Resource - Bitbucket Cloud REST API 9 | class BuildStatusApi < BaseApi 10 | include Tinybucket::Api::Helper::BuildStatusHelper 11 | 12 | attr_accessor :revision, :repo_owner, :repo_slug 13 | 14 | # Send 'GET a builds list for a commit' request 15 | # 16 | # @param options [Hash] 17 | # @return [Tinybucket::Model::Page] 18 | def list(options = {}) 19 | get_path( 20 | path_to_list, 21 | options, 22 | get_parser(:collection, Tinybucket::Model::BuildStatus) 23 | ) 24 | end 25 | 26 | # Send 'GET the build status for a commit' request 27 | # 28 | # @param revision [String] 29 | # @param key [String] 30 | # @param options [Hash] 31 | # @return [Tinybucket::Model::BuildStatus] 32 | def find(revision, key, options = {}) 33 | get_path( 34 | path_to_find(revision, key), 35 | options, 36 | get_parser(:object, Tinybucket::Model::BuildStatus) 37 | ) 38 | end 39 | 40 | # Send 'POST a build status for a commit' request 41 | # 42 | # @param revision [String] 43 | # @param key [String] 44 | # @param options [Hash] 45 | # @return [Tinybucket::Model::BuildStatus] 46 | def post(revision, key, options) 47 | post_path( 48 | path_to_post(revision), 49 | options.merge(key: key), 50 | get_parser(:object, Tinybucket::Model::BuildStatus) 51 | ) 52 | end 53 | 54 | # Send 'PUT a build status for a commit' request 55 | # 56 | # @param revision [String] 57 | # @param key [String] 58 | # @param options [Hash] 59 | # @return [Tinybucket::Model::BuildStatus] 60 | def put(revision, key, options) 61 | put_path( 62 | path_to_put(revision, key), 63 | options, 64 | get_parser(:object, Tinybucket::Model::BuildStatus) 65 | ) 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/resource/repos_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Resource::Repos do 4 | include ApiResponseMacros 5 | 6 | let(:options) { {} } 7 | let(:resource) { Tinybucket::Resource::Repos.new(owner, options) } 8 | 9 | describe 'Public Repos Resource' do 10 | let(:owner) { nil } 11 | 12 | describe '#create' do 13 | let(:params) { {} } 14 | subject { resource.create(params) } 15 | it { expect { subject }.to raise_error(NotImplementedError) } 16 | end 17 | 18 | describe '#find' do 19 | let(:params) { {} } 20 | subject { resource.find(params) } 21 | it { expect { subject }.to raise_error(NotImplementedError) } 22 | end 23 | 24 | describe 'Enumerable Methods' do 25 | let(:request_path) { '/repositories' } 26 | before { stub_enum_response(:get, request_path) } 27 | 28 | describe '#take(1)' do 29 | subject { resource.take(1) } 30 | it { expect(subject).to be_an_instance_of(Array) } 31 | end 32 | 33 | describe '#each' do 34 | it 'iterate models' do 35 | resource.each do |m| 36 | expect(m).to be_an_instance_of(Tinybucket::Model::Repository) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | 43 | describe 'Owner\'s Repos Resource' do 44 | let(:owner) { 'test_owner' } 45 | 46 | describe '#create' do 47 | let(:params) { {} } 48 | subject { resource.create(params) } 49 | it { expect { subject }.to raise_error(NotImplementedError) } 50 | end 51 | 52 | describe '#find' do 53 | let(:params) { {} } 54 | subject { resource.find(params) } 55 | it { expect { subject }.to raise_error(NotImplementedError) } 56 | end 57 | 58 | describe 'Enumerable Methods' do 59 | let(:request_path) { "/repositories/#{owner}" } 60 | before { stub_enum_response(:get, request_path) } 61 | 62 | describe '#take(1)' do 63 | subject { resource.take(1) } 64 | it { expect(subject).to be_an_instance_of(Array) } 65 | end 66 | 67 | describe '#each' do 68 | it 'iterate models' do 69 | resource.each do |m| 70 | expect(m).to be_an_instance_of(Tinybucket::Model::Repository) 71 | end 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/fixtures/users/test_owner/followers/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 10, 3 | "values": [ 4 | { 5 | "username": "RagnarArdal", 6 | "kind": "user", 7 | "website": null, 8 | "display_name": "Ragnar Árdal", 9 | "links": { 10 | "self": { 11 | "href": "https://api.bitbucket.org/2.0/users/RagnarArdal" 12 | }, 13 | "repositories": { 14 | "href": "https://api.bitbucket.org/2.0/users/RagnarArdal/repositories" 15 | }, 16 | "html": { 17 | "href": "https://api.bitbucket.org/RagnarArdal" 18 | }, 19 | "followers": { 20 | "href": "https://api.bitbucket.org/2.0/users/RagnarArdal/followers" 21 | }, 22 | "avatar": { 23 | "href": "https://secure.gravatar.com/avatar/27ca0dc0bb96722af4f0222f6260fbb5?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Fb376619db485%2Fimg%2Fdefault_avatar%2F32%2Fuser_blue.png&s=32" 24 | }, 25 | "following": { 26 | "href": "https://api.bitbucket.org/2.0/users/RagnarArdal/following" 27 | } 28 | }, 29 | "created_on": "2013-04-19T19:10:54.044200+00:00", 30 | "location": null, 31 | "type":"user" 32 | }, 33 | { 34 | "username": "ericko", 35 | "kind": "user", 36 | "website": "", 37 | "display_name": "ericko", 38 | "links": { 39 | "self": { 40 | "href": "https://api.bitbucket.org/2.0/users/ericko" 41 | }, 42 | "repositories": { 43 | "href": "https://api.bitbucket.org/2.0/users/ericko/repositories" 44 | }, 45 | "html": { 46 | "href": "https://api.bitbucket.org/ericko" 47 | }, 48 | "followers": { 49 | "href": "https://api.bitbucket.org/2.0/users/ericko/followers" 50 | }, 51 | "avatar": { 52 | "href": "https://secure.gravatar.com/avatar/d41d8cd98f00b204e9800998ecf8427e?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Fb376619db485%2Fimg%2Fdefault_avatar%2F32%2Fuser_blue.png&s=32" 53 | }, 54 | "following": { 55 | "href": "https://api.bitbucket.org/2.0/users/ericko/following" 56 | } 57 | }, 58 | "created_on": "2013-10-20T16:29:04.388755+00:00", 59 | "location": "", 60 | "type":"user" 61 | } 62 | ], 63 | "page": 1, 64 | "size": 2 65 | } 66 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/branch_restrictions_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::BranchRestrictionsApi do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:request_path) { nil } 9 | 10 | let(:api) do 11 | api = Tinybucket::Api::BranchRestrictionsApi.new 12 | api.repo_owner = owner 13 | api.repo_slug = slug 14 | api 15 | end 16 | 17 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 18 | 19 | before { stub_apiresponse(:get, request_path) if request_path } 20 | 21 | describe '#list' do 22 | subject { api.list } 23 | 24 | context 'without repo_owner and repo_slug' do 25 | let(:owner) { nil } 26 | let(:slug) { nil } 27 | it { expect { subject }.to raise_error(ArgumentError) } 28 | end 29 | 30 | context 'without repo_owner' do 31 | let(:owner) { nil } 32 | it { expect { subject }.to raise_error(ArgumentError) } 33 | end 34 | 35 | context 'without repo_slug' do 36 | let(:slug) { nil } 37 | it { expect { subject }.to raise_error(ArgumentError) } 38 | end 39 | 40 | context 'with repo_owner and repo_slug' do 41 | let(:request_path) do 42 | "/repositories/#{owner}/#{slug}/branch-restrictions" 43 | end 44 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 45 | end 46 | end 47 | 48 | describe '#find' do 49 | let(:restriction_id) { '1' } 50 | subject { api.find(restriction_id) } 51 | 52 | context 'without repo_owner and repo_slug' do 53 | let(:owner) { nil } 54 | let(:slug) { nil } 55 | it { expect { subject }.to raise_error(ArgumentError) } 56 | end 57 | 58 | context 'without repo_owner' do 59 | let(:owner) { nil } 60 | it { expect { subject }.to raise_error(ArgumentError) } 61 | end 62 | 63 | context 'without repo_slug' do 64 | let(:slug) { nil } 65 | it { expect { subject }.to raise_error(ArgumentError) } 66 | end 67 | 68 | context 'with repo_owner and repo_slug' do 69 | let(:request_path) do 70 | "/repositories/#{owner}/#{slug}/branch-restrictions/#{restriction_id}" 71 | end 72 | it 'return BranchRestriction Model' do 73 | expect(subject).to be_an_instance_of(Tinybucket::Model::BranchRestriction) 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "scm": "git", 3 | "has_wiki": false, 4 | "description": "", 5 | "links": { 6 | "watchers": { 7 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/watchers" 8 | }, 9 | "commits": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/commits" 11 | }, 12 | "self": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork" 14 | }, 15 | "html": { 16 | "href": "https://bitbucket.org/evzijst/atlassian-connect-fork" 17 | }, 18 | "avatar": { 19 | "href": "https://bitbucket.org/m/3a846b46851a/img/language-avatars/default_16.png" 20 | }, 21 | "forks": { 22 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/forks" 23 | }, 24 | "clone": [ 25 | { 26 | "href": "https://bitbucket.org/evzijst/atlassian-connect-fork.git", 27 | "name": "https" 28 | }, 29 | { 30 | "href": "ssh://git@bitbucket.org/evzijst/atlassian-connect-fork.git", 31 | "name": "ssh" 32 | } 33 | ], 34 | "pullrequests": { 35 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/pullrequests" 36 | } 37 | }, 38 | "fork_policy": "allow_forks", 39 | "language": "", 40 | "created_on": "2013-08-26T18:13:07.339080+00:00", 41 | "parent": { 42 | "links": { 43 | "self": { 44 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect" 45 | }, 46 | "avatar": { 47 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/m/3a846b46851a/img/language-avatars/default_16.png" 48 | } 49 | }, 50 | "full_name": "evzijst/atlassian-connect", 51 | "name": "atlassian-connect" 52 | }, 53 | "full_name": "evzijst/atlassian-connect-fork", 54 | "has_issues": false, 55 | "owner": { 56 | "username": "evzijst", 57 | "display_name": "Erik van Zijst", 58 | "links": { 59 | "self": { 60 | "href": "https://api.bitbucket.org/2.0/users/evzijst" 61 | }, 62 | "avatar": { 63 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Oct/28/evzijst-avatar-3454044670-3_avatar.png" 64 | } 65 | } 66 | }, 67 | "updated_on": "2013-08-26T18:13:07.514065+00:00", 68 | "size": 17657351, 69 | "is_private": false, 70 | "name": "atlassian-connect-fork" 71 | } 72 | -------------------------------------------------------------------------------- /lib/tinybucket/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Connection 5 | DEFAULT_USER_AGENT = 'Tinybucket Ruby Bitbucket REST client'.freeze 6 | 7 | def clear_cache 8 | @connection = nil 9 | end 10 | 11 | def caching? 12 | !@connection.nil? 13 | end 14 | 15 | def connection(parser = nil, options = {}) 16 | conn_options = default_options(options) 17 | clear_cache 18 | 19 | # TODO: cache connection for each (options, parser) pairs. 20 | Faraday.new( 21 | conn_options.merge(builder: stack(parser, options)) 22 | ) 23 | end 24 | 25 | private 26 | 27 | def default_options(_options) 28 | { 29 | headers: { 30 | USER_AGENT: Tinybucket.config.user_agent || DEFAULT_USER_AGENT 31 | }, 32 | ssl: { verify: false }, 33 | url: 'https://api.bitbucket.org/2.0'.freeze 34 | } 35 | end 36 | 37 | def default_middleware(_options) 38 | proc do |conn| 39 | configure_response_cache(conn) 40 | 41 | conn.request :multipart 42 | conn.request :url_encoded 43 | 44 | configure_auth(conn) 45 | 46 | conn.response :json, content_type: /\bjson$/ 47 | conn.use Tinybucket::Response::Handler 48 | conn.use :instrumentation 49 | 50 | conn.adapter Faraday.default_adapter 51 | end 52 | end 53 | 54 | def configure_auth(conn) 55 | if Tinybucket.config.access_token 56 | conn.request :oauth2, Tinybucket.config.access_token 57 | else 58 | oauth_secrets = { 59 | consumer_key: Tinybucket.config.oauth_token, 60 | consumer_secret: Tinybucket.config.oauth_secret 61 | } 62 | 63 | conn.request :oauth, oauth_secrets 64 | conn.response :follow_oauth_redirects, oauth_secrets 65 | end 66 | end 67 | 68 | def configure_response_cache(conn) 69 | return unless Tinybucket.config.cache_store_options 70 | 71 | conn.use :http_cache, Tinybucket.config.cache_store_options 72 | end 73 | 74 | def stack(parser, options = {}, &block) 75 | Faraday::RackBuilder.new(&block) and return if block_given? 76 | 77 | # TODO: cache stack for each (options, parser) pairs 78 | Faraday::RackBuilder.new do |conn| 79 | conn.use parser.type, parser_options: parser.options if parser.present? 80 | default_middleware(options).call(conn) 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/build_status_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::BuildStatusApi do 4 | include ApiResponseMacros 5 | 6 | let(:owner) { 'test_owner' } 7 | let(:slug) { 'test_repo' } 8 | let(:revision) { '1' } 9 | let(:status_key) { 'test_status' } 10 | let(:options) { {} } 11 | 12 | let(:api) do 13 | Tinybucket::Api::BuildStatusApi.new.tap do |api| 14 | api.repo_owner = owner 15 | api.repo_slug = slug 16 | api.revision = revision 17 | end 18 | end 19 | 20 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 21 | 22 | describe 'list' do 23 | subject { api.list(options) } 24 | 25 | context 'with owner and slug' do 26 | let(:request_path) { "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses" } 27 | before { stub_apiresponse(:get, request_path) } 28 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 29 | end 30 | end 31 | 32 | describe '#find' do 33 | let(:request_path) do 34 | "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses/build/#{status_key}" 35 | end 36 | subject { api.find(revision, status_key) } 37 | before { stub_apiresponse(:get, request_path) } 38 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::BuildStatus) } 39 | end 40 | 41 | describe '#post' do 42 | let(:request_path) do 43 | "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses/build" 44 | end 45 | let(:params) do 46 | { 47 | state: 'INPROGRESS', 48 | name: 'test_repo test #10', 49 | url: 'https://example.com/path/to/build/info', 50 | description: 'Changes by test_owner' 51 | } 52 | end 53 | 54 | subject { api.post(revision, status_key, params) } 55 | before { stub_apiresponse(:post, request_path) } 56 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::BuildStatus) } 57 | end 58 | 59 | describe '#put' do 60 | let(:request_path) do 61 | "/repositories/#{owner}/#{slug}/commit/#{revision}/statuses/build/#{status_key}" 62 | end 63 | let(:params) do 64 | { 65 | state: 'SUCCESSFUL', 66 | name: 'test_repo test #10', 67 | url: 'https://example.com/path/to/build/info', 68 | description: 'Changes by test_owner' 69 | } 70 | end 71 | 72 | subject { api.put(revision, status_key, params) } 73 | before { stub_apiresponse(:put, request_path) } 74 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::BuildStatus) } 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '21 9 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'ruby' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/model/team_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Model::Team do 4 | include ApiResponseMacros 5 | include ModelMacros 6 | 7 | let(:model_json) { load_json_fixture('team') } 8 | 9 | let(:request_path) { nil } 10 | 11 | let(:teamname) { 'test_team' } 12 | 13 | let(:model) do 14 | Tinybucket::Model::Team.new(model_json).tap do |m| 15 | m.username = teamname 16 | end 17 | end 18 | 19 | before { stub_apiresponse(:get, request_path) if request_path } 20 | 21 | it_behaves_like 'model has acceptable_attributes', 22 | Tinybucket::Model::Team, 23 | load_json_fixture('team') 24 | 25 | describe 'model can reloadable' do 26 | let(:team) do 27 | m = Tinybucket::Model::Team.new({}) 28 | m.username = teamname 29 | m 30 | end 31 | before { @model = team } 32 | it_behaves_like 'the model is reloadable' 33 | end 34 | 35 | describe '#members' do 36 | let(:request_path) { "/teams/#{teamname}/members" } 37 | subject { model.members } 38 | it 'return resource' do 39 | expect(subject).to be_an_instance_of( 40 | Tinybucket::Resource::Team::Members) 41 | end 42 | end 43 | 44 | describe '#followers' do 45 | let(:request_path) { "/teams/#{teamname}/followers" } 46 | subject { model.followers() } 47 | it 'return resource' do 48 | expect(subject).to be_an_instance_of( 49 | Tinybucket::Resource::Team::Followers) 50 | end 51 | end 52 | 53 | describe '#following' do 54 | let(:request_path) { "/teams/#{teamname}/following" } 55 | subject { model.following } 56 | it 'return resource' do 57 | expect(subject).to be_an_instance_of( 58 | Tinybucket::Resource::Team::Following) 59 | end 60 | end 61 | 62 | describe '#repos' do 63 | let(:request_path) { "/teams/#{teamname}/repositories" } 64 | subject { model.repos } 65 | it 'return resource' do 66 | expect(subject).to be_an_instance_of( 67 | Tinybucket::Resource::Team::Repos) 68 | end 69 | end 70 | 71 | describe '#projects' do 72 | let(:request_path) { "/teams/#{teamname}/projects/" } 73 | subject { model.projects } 74 | it 'return resource' do 75 | expect(subject).to be_an_instance_of( 76 | Tinybucket::Resource::Projects) 77 | end 78 | end 79 | 80 | describe '#project' do 81 | let(:project_key) { 'myprj' } 82 | let(:request_path) { "/teams/#{teamname}/projects/#{project_key}" } 83 | subject { model.project(project_key) } 84 | it 'return Project model' do 85 | expect(subject).to be_an_instance_of( 86 | Tinybucket::Model::Project) 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/test_owner/test_repo/forks/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 10, 3 | "values": [ 4 | { 5 | "scm": "git", 6 | "has_wiki": false, 7 | "description": "", 8 | "links": { 9 | "watchers": { 10 | "href": "https://bitbucket.org/api/2.0/repositories/bnguyen/markdowndemo/watchers" 11 | }, 12 | "commits": { 13 | "href": "https://bitbucket.org/api/2.0/repositories/bnguyen/markdowndemo/commits" 14 | }, 15 | "self": { 16 | "href": "https://bitbucket.org/api/2.0/repositories/bnguyen/markdowndemo" 17 | }, 18 | "html": { 19 | "href": "https://bitbucket.org/bnguyen/markdowndemo" 20 | }, 21 | "avatar": { 22 | "href": "https://d3oaxc4q5k2d6q.cloudfront.net/m/f5a36db3429b/img/language-avatars/default_16.png" 23 | }, 24 | "forks": { 25 | "href": "https://bitbucket.org/api/2.0/repositories/bnguyen/markdowndemo/forks" 26 | }, 27 | "clone": [ 28 | { 29 | "href": "https://bitbucket.org/bnguyen/markdowndemo.git", 30 | "name": "https" 31 | }, 32 | { 33 | "href": "ssh://git@bitbucket.org/bnguyen/markdowndemo.git", 34 | "name": "ssh" 35 | } 36 | ], 37 | "pullrequests": { 38 | "href": "https://bitbucket.org/api/2.0/repositories/bnguyen/markdowndemo/pullrequests" 39 | } 40 | }, 41 | "fork_policy": "allow_forks", 42 | "language": "", 43 | "created_on": "2013-10-14T20:50:02.321710+00:00", 44 | "parent": { 45 | "links": { 46 | "self": { 47 | "href": "https://bitbucket.org/api/2.0/repositories/tutorials/markdowndemo" 48 | }, 49 | "avatar": { 50 | "href": "https://d3oaxc4q5k2d6q.cloudfront.net/m/f5a36db3429b/img/language-avatars/default_16.png" 51 | } 52 | }, 53 | "full_name": "tutorials/markdowndemo", 54 | "name": "MarkdownDemo" 55 | }, 56 | "full_name": "bnguyen/markdowndemo", 57 | "has_issues": false, 58 | "owner": { 59 | "username": "bnguyen", 60 | "display_name": "Brian Nguyen", 61 | "links": { 62 | "self": { 63 | "href": "https://bitbucket.org/api/2.0/users/bnguyen" 64 | }, 65 | "avatar": { 66 | "href": "https://secure.gravatar.com/avatar/f7a8180e50193e2b1ececace9ca8d162?d=https%3A%2F%2Fd3oaxc4q5k2d6q.cloudfront.net%2Fm%2Ff5a36db3429b%2Fimg%2Fdefault_avatar%2F32%2Fuser_blue.png&s=32" 67 | } 68 | } 69 | }, 70 | "updated_on": "2013-10-14T20:50:02.382945+00:00", 71 | "size": 860675, 72 | "is_private": false, 73 | "name": "markdowndemo" 74 | } 75 | ], 76 | "page": 1, 77 | "size": 1 78 | } 79 | -------------------------------------------------------------------------------- /lib/tinybucket/api/team_api.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Tinybucket 4 | module Api 5 | # Team Api client 6 | # 7 | # @see https://developer.atlassian.com/bitbucket/api/2/reference/resource/teams 8 | # teams Endpoint 9 | class TeamApi < BaseApi 10 | include Tinybucket::Api::Helper::TeamHelper 11 | 12 | # Send 'GET teams' request 13 | # 14 | # @param role_name [String] role name 15 | # @param options [Hash] 16 | # @return [Tinybucket::Model::Page] 17 | def list(role_name, options = {}) 18 | get_path( 19 | path_to_list, 20 | { role: role_name }.merge(options), 21 | get_parser(:collection, Tinybucket::Model::Team) 22 | ) 23 | end 24 | 25 | # Send 'GET the team profile' request 26 | # 27 | # @param name [String] The team's name 28 | # @param options [Hash] 29 | # @return [Tinybucket::Model::Team] 30 | def find(name, options = {}) 31 | get_path( 32 | path_to_find(name), 33 | options, 34 | get_parser(:object, Tinybucket::Model::Team) 35 | ) 36 | end 37 | 38 | # Send 'GET the team members' request 39 | # 40 | # @param name [String] The team's name 41 | # @param options [Hash] 42 | # @return [Tinybucket::Model::Page] 43 | def members(name, options = {}) 44 | get_path( 45 | path_to_members(name), 46 | options, 47 | get_parser(:collection, Tinybucket::Model::Team) 48 | ) 49 | end 50 | 51 | # Send 'GET the list of followers' request 52 | # 53 | # @param name [String] The team's name 54 | # @param options [Hash] 55 | # @return [Tinybucket::Model::Page] 56 | def followers(name, options = {}) 57 | get_path( 58 | path_to_followers(name), 59 | options, 60 | get_parser(:collection, Tinybucket::Model::Team) 61 | ) 62 | end 63 | 64 | # Send 'GET a lisf of accounts the team is following' request 65 | # 66 | # @param name [String] The team's name 67 | # @param options [Hash] 68 | # @return [Tinybucket::Model::Page] 69 | def following(name, options = {}) 70 | get_path( 71 | path_to_following(name), 72 | options, 73 | get_parser(:collection, Tinybucket::Model::Team) 74 | ) 75 | end 76 | 77 | # Send 'GET the team's repositories' request 78 | # 79 | # @param name [String] The team's name 80 | # @param options [Hash] 81 | # @return [Tinybucket::Model::Page] 82 | def repos(name, options = {}) 83 | get_path( 84 | path_to_repos(name), 85 | options, 86 | get_parser(:collection, Tinybucket::Model::Repository) 87 | ) 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/lib/tinybucket/api/team_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Tinybucket::Api::TeamApi do 4 | include ApiResponseMacros 5 | 6 | let(:api) { Tinybucket::Api::TeamApi.new } 7 | let(:teamname) { 'test_team' } 8 | let(:request_path) { nil } 9 | before { stub_apiresponse(:get, request_path) if request_path } 10 | 11 | it { expect(api).to be_a_kind_of(Tinybucket::Api::BaseApi) } 12 | 13 | describe 'list' do 14 | subject { api.list(role_name) } 15 | 16 | let(:role_name) { "admin" } 17 | let(:request_path) { "/teams?role=admin" } 18 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 19 | end 20 | 21 | describe 'profile' do 22 | subject { api.find(teamname) } 23 | 24 | context 'when without teamname' do 25 | let(:teamname) { nil } 26 | it { expect { subject }.to raise_error(ArgumentError) } 27 | end 28 | 29 | context 'when with teamname' do 30 | let(:request_path) { "/teams/#{teamname}" } 31 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Team) } 32 | end 33 | end 34 | 35 | describe 'members' do 36 | subject { api.members(teamname) } 37 | 38 | context 'when without teamname' do 39 | let(:teamname) { nil } 40 | it { expect { subject }.to raise_error(ArgumentError) } 41 | end 42 | 43 | context 'when with teamname' do 44 | let(:request_path) { "/teams/#{teamname}/members" } 45 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 46 | end 47 | end 48 | 49 | describe 'followers' do 50 | subject { api.followers(teamname) } 51 | 52 | context 'when without teamname' do 53 | let(:teamname) { nil } 54 | it { expect { subject }.to raise_error(ArgumentError) } 55 | end 56 | 57 | context 'when with teamname' do 58 | let(:request_path) { "/teams/#{teamname}/followers" } 59 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 60 | end 61 | end 62 | 63 | describe 'following' do 64 | subject { api.following(teamname) } 65 | 66 | context 'when without teamname' do 67 | let(:teamname) { nil } 68 | it { expect { subject }.to raise_error(ArgumentError) } 69 | end 70 | 71 | context 'when with teamname' do 72 | let(:request_path) { "/teams/#{teamname}/following" } 73 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 74 | end 75 | end 76 | 77 | describe 'repos' do 78 | subject { api.repos(teamname) } 79 | 80 | context 'when without teamname' do 81 | let(:teamname) { nil } 82 | it { expect { subject }.to raise_error(ArgumentError) } 83 | end 84 | 85 | context 'when with teamname' do 86 | let(:request_path) { "/teams/#{teamname}/repositories" } 87 | it { expect(subject).to be_an_instance_of(Tinybucket::Model::Page) } 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/fixtures/repositories/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "pagelen": 10, 3 | "values": [ 4 | { 5 | "scm": "git", 6 | "has_wiki": false, 7 | "description": "", 8 | "links": { 9 | "watchers": { 10 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/watchers" 11 | }, 12 | "commits": { 13 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/commits" 14 | }, 15 | "self": { 16 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork" 17 | }, 18 | "html": { 19 | "href": "https://bitbucket.org/evzijst/atlassian-connect-fork" 20 | }, 21 | "avatar": { 22 | "href": "https://bitbucket.org/m/3a846b46851a/img/language-avatars/default_16.png" 23 | }, 24 | "forks": { 25 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/forks" 26 | }, 27 | "clone": [ 28 | { 29 | "href": "https://bitbucket.org/evzijst/atlassian-connect-fork.git", 30 | "name": "https" 31 | }, 32 | { 33 | "href": "ssh://git@bitbucket.org/evzijst/atlassian-connect-fork.git", 34 | "name": "ssh" 35 | } 36 | ], 37 | "pullrequests": { 38 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect-fork/pullrequests" 39 | } 40 | }, 41 | "fork_policy": "allow_forks", 42 | "language": "", 43 | "created_on": "2013-08-26T18:13:07.339080+00:00", 44 | "parent": { 45 | "links": { 46 | "self": { 47 | "href": "https://api.bitbucket.org/2.0/repositories/evzijst/atlassian-connect" 48 | }, 49 | "avatar": { 50 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/m/3a846b46851a/img/language-avatars/default_16.png" 51 | } 52 | }, 53 | "full_name": "evzijst/atlassian-connect", 54 | "name": "atlassian-connect" 55 | }, 56 | "full_name": "evzijst/atlassian-connect-fork", 57 | "has_issues": false, 58 | "owner": { 59 | "username": "evzijst", 60 | "display_name": "Erik van Zijst", 61 | "links": { 62 | "self": { 63 | "href": "https://api.bitbucket.org/2.0/users/evzijst" 64 | }, 65 | "avatar": { 66 | "href": "https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Oct/28/evzijst-avatar-3454044670-3_avatar.png" 67 | } 68 | } 69 | }, 70 | "updated_on": "2013-08-26T18:13:07.514065+00:00", 71 | "size": 17657351, 72 | "is_private": false, 73 | "name": "atlassian-connect-fork" 74 | } 75 | ], 76 | "page": 1, 77 | "next": "https://api.bitbucket.org/2.0/repositories?pagelen=1&after=2013-09-26T23%3A01%3A01.638828%2B00%3A00&page=2" 78 | } 79 | --------------------------------------------------------------------------------