First release

This commit is contained in:
Mariani Lucas 2020-08-22 21:54:24 -03:00
commit 26053b7bbe
56 changed files with 2322 additions and 0 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
.git
Gemfile.lock
Dockerfile
docker-compose.yml
*.gem
deploy

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
*.gem
*.rbc
*.swp
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
vendor/bundle
.idea
.ruby-version
.ruby-gemset
test.rb

44
.rubocop.yml Normal file
View File

@ -0,0 +1,44 @@
---
inherit_from: .rubocop_todo.yml
require: rubocop-performance
AllCops:
TargetRubyVersion: 2.5
NewCops: enable
Layout/LineLength:
Max: 123
Exclude:
- 'lib/sonarqube/client/*'
- 'spec/**/*'
Metrics/BlockLength:
Exclude:
- 'spec/**/*'
Style/Documentation:
Enabled: false
Style/ClassAndModuleChildren:
Exclude:
- 'lib/sonarqube/*'
- 'lib/sonarqube/client/*'
Lint/NonDeterministicRequireOrder:
Enabled: false
Style/HashEachMethods:
Enabled: true
Style/HashTransformKeys:
Enabled: true
Style/HashTransformValues:
Enabled: true
Style/OptionalBooleanParameter:
Enabled: false
Lint/MissingSuper:
Enabled: false

44
.rubocop_todo.yml Normal file
View File

@ -0,0 +1,44 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2020-06-28 04:00:53 UTC using RuboCop version 0.86.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 4
# Configuration parameters: IgnoredMethods.
Metrics/AbcSize:
Max: 34
# Offense count: 3
# Configuration parameters: IgnoredMethods.
Metrics/CyclomaticComplexity:
Max: 13
# Offense count: 8
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/MethodLength:
Max: 34
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 156
# Offense count: 2
# Configuration parameters: CountKeywordArgs.
Metrics/ParameterLists:
Max: 6
# Offense count: 1
# Configuration parameters: IgnoredMethods.
Metrics/PerceivedComplexity:
Max: 10
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect.
Security/JSONLoad:
Exclude:
- 'lib/sonarqube/request.rb'

9
CHANGELOG.md Normal file
View File

@ -0,0 +1,9 @@
## CHANGELOG
### Newer releases
Please see: https://github.com/psyreactor/sonarqube-ruby/releases
### 1.0.0 (19/08/2020)
-

195
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,195 @@
# Contributing to Sonarqube
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved!
## Using the issue tracker
You can use the issues tracker for:
* [bug reports](#bug-reports)
* [feature requests](#feature-requests)
* [submitting pull requests](#pull-requests)
Use [Stackoverflow](http://stackoverflow.com/) for questions and personal support requests.
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** — check if the issue has already been
reported.
2. **Check if the issue has been fixed** — try to reproduce it using the
`master` branch in the repository.
3. **Isolate and report the problem** — ideally create a reduced test
case.
Please try to be as detailed as possible in your report. Include information about
your Ruby, Sonarqube client and Sonarqube instance versions. Please provide steps to
reproduce the issue as well as the outcome you were expecting! All these details
will help developers to fix any potential bugs.
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the environment in which it occurs. If suitable,
> include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the community of the merits of this feature.
Please provide as much detail and context as possible.
## Contributing Documentation
Code documentation has a special convention: it uses [YARD](http://yardoc.org/)
formatting and the first paragraph is considered to be a short summary.
For methods say what it will do. For example write something like:
```ruby
# Reverses the contents of a String or IO object.
#
# @param [String, #read] contents the contents to reverse
# @return [String] the contents reversed lexically
def reverse(contents)
contents = contents.read if contents.respond_to? :read
contents.reverse
end
```
For classes, modules say what it is. For example write something like:
```ruby
# Defines methods related to groups.
module Groups
```
Keep in mind that the documentation notes might show up in a summary somewhere,
long texts in the documentation notes create very ugly summaries. As a rule of thumb
anything longer than 80 characters is too long.
Try to keep unnecessary details out of the first paragraph, it's only there to
give a user a quick idea of what the documented "thing" does/is. The rest of the
documentation notes can contain the details, for example parameters and what
is returned.
If possible include examples. For example:
```ruby
# Gets information about a project.
#
# @example
# Sonarqube.project(3)
# Sonarqube.project('sonarqube')
#
# @param [Integer, String] id The ID or name of a project.
# @return [Sonarqube::ObjectifiedHash]
def project(id)
```
This makes it easy to test the examples so that they don't go stale and examples
are often a great help in explaining what a method does.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**IMPORTANT**: By submitting a patch, you agree that your work will be
licensed under the license used by the project.
If you have any large pull request in mind (e.g. implementing features,
refactoring code, etc), **please ask first** otherwise you risk spending
a lot of time working on something that the project's developers might
not want to merge into the project.
Please adhere to the coding conventions in the project (indentation,
accurate comments, etc.) and don't forget to add your own tests and
documentation. When working with git, we recommend the following process
in order to craft an excellent pull request:
1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork,
and configure the remotes:
```sh
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/sonarqube
# Navigate to the newly cloned directory
cd sonarqube
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/psyreactor/sonarqube
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout master
git pull upstream master
```
3. Create a new topic branch (off of `master`) to contain your feature, change,
or fix.
**IMPORTANT**: Making changes in `master` is discouraged. You should always
keep your local `master` in sync with upstream `master` and make your
changes in topic branches.
```sh
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Keep your commit messages organized,
with a short description in the first line and more detailed information on
the following lines. Feel free to use Git's
[interactive rebase](https://help.github.com/articles/about-git-rebase/)
feature to tidy up your commits before making them public.
5. Make sure all the tests are still passing.
```sh
rake
```
6. Push your topic branch up to your fork:
```sh
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
8. If you haven't updated your pull request for a while, you should consider
rebasing on master and resolving any conflicts.
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
should always `git rebase` on `master` to bring your changes up to date when
necessary.
```sh
git checkout master
git pull upstream master
git checkout <your-topic-branch>
git rebase master
```
Thank you for your contributions!

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM ruby:2.7
WORKDIR /app
COPY . ./
RUN bundle install
CMD ["irb"]

10
Gemfile Normal file
View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
source 'https://rubygems.org'
# Specify your gem's dependencies in sonarqube.gemspec
gemspec
gem 'pry'
gem 'rubocop'
gem 'rubocop-performance'

24
LICENSE.txt Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2020 Lucas Mariani <marianilucas@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

250
README.md Normal file
View File

@ -0,0 +1,250 @@
# Sonarqube
[![Build Status](https://img.shields.io/github/workflow/status/psyreactor/sonarqube/CI/master)](https://github.com/psyreactor/sonarqube/actions?query=workflow%3ARuby)
[![Inline docs](https://inch-ci.org/github/psyreactor/sonarqube.svg)](https://inch-ci.org/github/psyreactor/sonarqube)
[![Gem version](https://img.shields.io/gem/v/sonarqube.svg)](https://rubygems.org/gems/sonarqube)
[![License](https://img.shields.io/badge/license-BSD-red.svg)](https://github.com/psyreactor/sonarqube/blob/master/LICENSE.txt)
[website](https://psyreactor.github.io/sonarqube) |
[documentation](https://www.rubydoc.info/gems/sonarqube/frames)
Sonarqube is a Ruby wrapper and CLI for the Sonarqube API
As of version `1.0.0` this gem only supports Sonarqube 7.9.
## Installation
Install it from rubygems:
```sh
gem install sonarqube
```
Or add to a Gemfile:
```ruby
gem 'sonarqube'
# gem 'sonarqube', github: 'psyreactor/sonarqube'
```
Mac OS users can install using Homebrew (may not be the latest version):
```sh
brew install sonarqube-gem
```
## Usage
### Configuration example
```ruby
Sonarqube.configure do |config|
config.endpoint = 'https://example.net:9000' # API endpoint URL, default: ENV['SONARQUBE_API_ENDPOINT']
config.private_token = 'Dfrt938dSgAOWd4' # user's private token, default: ENV['SONARQUBE_API_PRIVATE_TOKEN']
# Optional
# config.user_agent = 'Custom User Agent' # user agent, default: 'Sonarqube Ruby Gem [version]'
end
```
### Usage examples
```ruby
# set an API endpoint
Sonarqube.endpoint = 'https://example.net:9000'
# => "https://example.net:9000"
# set a user private token
Sonarqube.private_token = 'Dfrt938dSgAOWd4'
# => "Dfrt938dSgAOWd4"
# configure a proxy server
Sonarqube.http_proxy('proxyhost', 8888)
# proxy server with basic auth
Sonarqube.http_proxy('proxyhost', 8888, 'proxyuser', 'strongpasswordhere')
# set timeout for responses
ENV['SONARQUBE_API_HTTPARTY_OPTIONS'] = '{read_timeout: 60}'
```
### initialize a new client with custom headers
```ruby
g = Sonarqube.client(
endpoint: 'https://example.com:9000',
private_token: 'Dfrt938dSgAOWd4',
httparty: {
headers: { 'Cookie' => 'sonarqube_canary=true' }
}
)
# => #<Sonarqube::Client:0x00000001e62408 @endpoint="https://api.example.com", @private_token="qEsq1pt6HJPaNciie3MG", @user_agent="Sonarqube Ruby Gem 2.0.0">
```
### Projects
#### Create Project
```ruby
project = Sonarqube.project_create('new_project')
# => <Sonarqube::ObjectifiedHash:46200 {hash: {"project"=>{"key"=>"new_project", "name"=>"new_project", "qualifier"=>"TRK", "visibility"=>"public"}}}
project.project.key
# => "new_project"
project.to_hash
# => {"project"=>{"key"=>"new_project", "name"=>"new_project", "qualifier"=>"TRK", "visibility"=>"public"}}
```
#### Delete Project
```ruby
project = Sonarqube.project_delete('test')
# => #<Sonarqube::ObjectifiedHash:46220 {hash: {}}
project.to_hash.empty?
# => true
```
#### Search Project
```ruby
projects = Sonarqube.project_search()
# => #<Sonarqube::ObjectifiedHash:46240 {hash: {"paging"=>{"pageIndex"=>1, "pageSize"=>100, "total"=>2}, "components"=>[{"organization"=>"default-organization", "key"=>"old_key", "name"=>"new_proyect", "qualifier"=>"TRK", "visibility"=>"private"}, {"organization"=>"default-organization", "key"=>"test", "name"=>"test", "qualifier"=>"TRK", "visibility"=>"public"}]}}
projects.components.each do | project |
puts "name: #{project.name}"
puts "key: #{project.key}"
end
# name: new_proyect
# key: old_key
# name: test
# key: test
```
### Users
#### Create User
```ruby
user = Sonarqube.user_create('new_user', 'key_new_user' ,'secretpassword')
# => #<Sonarqube::ObjectifiedHash:46320 {hash: {"user"=>{"login"=>"login_name", "name"=>"name_user", "scmAccounts"=>[], "active"=>true, "local"=>true}}}
user.user.login
# login_name
user.user.name
# name_user
```
#### Delete User
```ruby
user = Sonarqube.user_delete('test')
# => #<Sonarqube::ObjectifiedHash:46220 {hash: {}}
user.to_hash.empty?
# => true
```
#### Search User
```ruby
users = Sonarqube.users_search()
# => #<Sonarqube::ObjectifiedHash:46340 {hash: {"paging"=>{"pageIndex"=>1, "pageSize"=>50, "total"=>5}, "users"=>[{"login"=>"admin", "name"=>"Administrator", "active"=>true, "groups"=>["sonar-administrators", "sonar-users"], "tokensCount"=>1, "local"=>true, "externalIdentity"=>"admin", "externalProvider"=>"sonarqube", "lastConnectionDate"=>"2020-08-22T23:09:14+0000"}, {"login"=>"new_user", "name"=>"key_new_user", "active"=>true, "groups"=>["sonar-users"], "tokensCount"=>0, "local"=>true, "externalIdentity"=>"new_user", "externalProvider"=>"sonarqube"}, {"login"=>"login_name", "name"=>"name_user", "active"=>true, "groups"=>["sonar-users"], "tokensCount"=>0, "local"=>true, "externalIdentity"=>"login_name", "externalProvider"=>"sonarqube"}, {"login"=>"test3", "name"=>"test QA", "active"=>true, "groups"=>["sonar-users"], "tokensCount"=>0, "local"=>true, "externalIdentity"=>"test3", "externalProvider"=>"sonarqube"}, {"login"=>"newlogin", "name"=>"test QA", "active"=>true, "groups"=>["sonar-users"], "tokensCount"=>0, "local"=>true, "externalIdentity"=>"newlogin", "externalProvider"=>"sonarqube"}]}}
users.users.each do | user |
puts "name: #{user.name}"
puts "login: #{user.login}"
puts "lastConection: #{user.lastConnectionDate}" if defined? user.lastConnectionDate
end
# name: Administrator
# login: admin
# lastConection: 2020-08-22T23:09:14+0000
# name: key_new_user
# login: new_user
# name: name_user
# login: login_name
# name: test QA
# login: test3
# name: test QA
# login: newlogin
```
### Groups
#### Create Group
```ruby
group = Sonarqube.create_group('New-Group', {description: 'Sonarqube group users'})
# => #<Sonarqube::ObjectifiedHash:46500 {hash: {"group"=>{"uuid"=>"AXQYrrgCsrvdoo0YodNM", "organization"=>"default-organization", "name"=>"New-Group", "description"=>"Sonarqube group users", "membersCount"=>0, "default"=>false}}}
group.group.uuid
# AXQYrrgCsrvdoo0YodNM
group.group.description
# Sonarqube group users
```
#### Delete Group
```ruby
group = Sonarqube.group_delete('New-Group')
# => #<Sonarqube::ObjectifiedHash:46220 {hash: {}}
group.to_hash.empty?
# => true
```
#### Search Group
```ruby
groups = Sonarqube.search_groups({ q: 'sonar-users' })
# => #<Sonarqube::ObjectifiedHash:46520 {hash: {"paging"=>{"pageIndex"=>1, "pageSize"=>100, "total"=>1}, "groups"=>[{"uuid"=>"AXOt93S3gMZPhbn-E_O7", "name"=>"sonar-users", "description"=>"Any new users created will automatically join this group", "membersCount"=>5, "default"=>true}]}}
groups.groups.each do | group |
puts "name: #{group.name}"
puts "login: #{group.description}"
puts "membersCount: #{group.membersCount}"
end
# name: sonar-users
# description: Any new users created will automatically join this group
# MembersCount: 5
```
For more information, refer to [documentation](https://www.rubydoc.info/gems/sonarqube/frames).
## Development
### With a dockerized Sonarqube instance
```shell
docker-compose up -d sonarqube # Will start the Sonarqube instance in the background (approx. 3 minutes)
```
After a while, your Sonarqube instance will be accessible on http://localhost:9000.
You can login with the admin/admin user/password.
You can now setup a personal access token here: http://localhost:9000
Once you have your token, set the variables to the correct values in the `docker.env` file.
Then, launch the tool:
```shell
docker-compose run app
```
```ruby
Sonarqube.users
=> [#<Sonarqube::ObjectifiedHash:47231290771040 {hash: {"id"=>1, "name"=>"Administrator", "username"=>"root", ...]
```
To launch the specs:
```shell
docker-compose run app rake spec
```
### With an external Sonarqube instance
First, set the variables to the correct values in the `docker.env` file.
Then, launch the tool:
```shell
docker-compose run app
```
```ruby
Sonarqube.users
=> [#<Sonarqube::ObjectifiedHash:47231290771040 {hash: {"id"=>1, "name"=>"Administrator", "username"=>"root", ...]
```
To launch the specs,
```shell
docker-compose run app rake spec
```
For more information see [CONTRIBUTING.md](https://github.com/psyreactor/sonarqube/blob/master/CONTRIBUTING.md).
## License
Released under the BSD 2-clause license. See LICENSE.txt for details.

15
Rakefile Normal file
View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.rspec_opts = ['--color', '--format d']
end
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop) do |task|
task.options = ['-D', '--parallel']
end
task default: :spec

40
docker-compose.yml Normal file
View File

@ -0,0 +1,40 @@
---
version: '3'
services:
app:
build: .
volumes:
- ./:/app
env_file: docker.env
sonarqube-postgresql:
image: docker.bintray.io/postgres:9.6.11
container_name: sonarqube-postgresql
ports:
- 5433:5433
environment:
POSTGRES_DB: sonar
POSTGRES_USER: sonar
POSTGRES_PASSWORD: P4ssw0rd
command: -p 5433
volumes:
- sonarqube-postgresql:/var/lib/postgresql/data
sonarqube:
image: sonarqube
container_name: sonarqube
depends_on:
- sonarqube-postgresql
links:
- sonarqube-postgresql
ports:
- 9000:9000
environment:
SONARQUBE_JDBC_URL: jdbc:postgresql://localhost:5433/sonar
SONARQUBE_JDBC_USERNAME: sonar
SONARQUBE_JDBC_PASSWORD: P4ssw0rd
volumes:
- sonarqube_conf:/opt/sonarqube/conf
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_bundled-plugins:/opt/sonarqube/lib/bundled-plugins

2
docker.env Normal file
View File

@ -0,0 +1,2 @@
SONAR_API_ENDPOINT=http://localhost:9000
SONAR_API_PRIVATE_TOKEN=g98hwj_dbt_lkq029i

46
lib/sonarqube.rb Normal file
View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'sonarqube/version'
require 'sonarqube/objectified_hash'
require 'sonarqube/configuration'
require 'sonarqube/error'
require 'sonarqube/request'
require 'sonarqube/api'
require 'sonarqube/client'
module Sonarqube
extend Configuration
# Alias for Sonarqube::Client.new
#
# @return [Sonarqube::Client]
def self.client(options = {})
Sonarqube::Client.new(options)
end
# Delegate to Sonarqube::Client
def self.method_missing(method, *args, &block)
return super unless client.respond_to?(method)
client.send(method, *args, &block)
end
# Delegate to Sonarqube::Client
def self.respond_to_missing?(method_name, include_private = false)
client.respond_to?(method_name) || super
end
# Delegate to HTTParty.http_proxy
def self.http_proxy(address = nil, port = nil, username = nil, password = nil)
Sonarqube::Request.http_proxy(address, port, username, password)
end
# Returns an unsorted array of available client methods.
#
# @return [Array<Symbol>]
def self.actions
hidden =
/endpoint|private_token|auth_token|user_agent|get|post|put|\Adelete\z|validate\z|request_defaults|httparty/
(Sonarqube::Client.instance_methods - Object.methods).reject { |e| e[hidden] }
end
end

22
lib/sonarqube/api.rb Normal file
View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Sonarqube
# @private
class API < Request
# @private
attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
# @private
alias auth_token= private_token=
# Creates a new API.
# @raise [Error:MissingCredentials]
def initialize(options = {})
options = Sonarqube.options.merge(options)
(Configuration::VALID_OPTIONS_KEYS + [:auth_token]).each do |key|
send("#{key}=", options[key]) if options[key]
end
request_defaults
self.class.headers 'User-Agent' => user_agent
end
end
end

36
lib/sonarqube/client.rb Normal file
View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Sonarqube
# Wrapper for the Sonarqube REST API.
class Client < API
Dir[File.expand_path('client/*.rb', __dir__)].each { |f| require f }
# Please keep in alphabetical order
include Groups
include Projects
include Users
# Text representation of the client, masking private token.
#
# @return [String]
def inspect
inspected = super
inspected.sub! @private_token, only_show_last_four_chars(@private_token) if @private_token
inspected
end
# Utility method for URL encoding of a string.
# Copied from https://ruby-doc.org/stdlib-2.7.0/libdoc/erb/rdoc/ERB/Util.html
#
# @return [String]
def url_encode(url)
url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString, Style/FormatStringToken
end
private
def only_show_last_four_chars(token)
"#{'*' * (token.size - 4)}#{token[-4..-1]}"
end
end
end

View File

@ -0,0 +1,124 @@
# frozen_string_literal: true
class Sonarqube::Client
# Defines methods related to groups.
# @see https://SONAR_URL/web_api/api/user_groups
module Groups
# Search for user groups.
#
# @example
# Sonarqube.groups_search
# Sonarqube.groups_search({ ps: 3, p: 2 })
# Sonarqube.groups_search({ ps: 3, p: 2 , q: sonar-users})
#
# @param [Hash] options A customizable set of options.
# @option options [String] :f Comma-separated list of the fields to be returned in response. All the fields are returned by default.
# @option options [Integer] :ps Page size number of projects to return per page
# @option options [Integer] :p The page to retrieve
# @option options [String] :q Limit search to names that contain the supplied string.
# (Any provided options will be passed to Sonarqube. See {https://SONAR_URL/web_api/api/user_groups/search}
# @return [Array<Sonarqube::ObjectifiedHash>]
def search_groups(options = {})
get('/api/user_groups/search', query: options)
end
alias groups_search search_groups
# Creates a new group.
#
# @example
# Sonarqube.create_group('new-group')
# Sonarqube.create_group('sonarqube', { description: 'New Sonarqube project' })
#
# @param [String] name(required) The name of a group.
# @param [Hash] options A customizable set of options.
# @option options [String] :description Description for the new group. A group description cannot be larger than 200 characters.
# @return [Sonarqube::ObjectifiedHash] Information about created group.
def create_group(name, options = {})
body = { name: name }.merge(options)
post('/api/user_groups/create', body: body)
end
alias group_create create_group
# Delete's a group.
#
# @example
# Sonarqube.delete_group('projecto')
#
# @param [String] name(required) The name of a group
# @return [Sonarqube::ObjectifiedHash] Empty hash response.
def delete_group(name)
post('/api/user_groups/delete', body: { name: name })
end
alias group_delete delete_group
# Update group.
#
# @example
# Sonarqube.group_members('AXQRcKrW9pRiZzanEJ2E')
# Sonarqube.group_members('AXQRcKrW9pRiZzanEJ2E, { description: 'update group description })
#
# @param [String] id(required) The ID of a group.
# @param [Hash] options A customizable set of options.
# @option options [String] :description New optional description for the group. A group description cannot be larger than 200 characters. If value is not defined, then description is not changed.
# @option options [String] :name New optional name for the group. A group name cannot be larger than 255 characters and must be unique. Value 'anyone' (whatever the case) is reserved and cannot be used. If value is empty or not defined, then name is not changed.
# @return [Array<Sonarqube::ObjectifiedHash>]
def update_group(id, options = {})
post('/api/user_groups/update', body: { id: id }.merge!(options))
end
alias group_update update_group
# Add a user to a group.
#
# @example
# Sonarqube.add_member('AXQRcKrW9pRiZzanEJ2E', 'test-user')
# Sonarqube.add_member('AXQRcKrW9pRiZzanEJ2E', 'test-user', {name: 'sonar-groups'})
#
# @param [String] id(required) The id of group.
# @param [String] login(required) The login of user.
# @param [Hash] options A customizable set of options.
# @option options [String] :name Optional name of group.
# @return [Sonarqube::ObjectifiedHash]
def add_member(id = nil, login = nil, options = {})
raise ArgumentError, 'Missing required parameters' if id.nil? || login.nil?
post('/api/user_groups/add_user', body: { id: id, login: login }.merge!(options))
end
alias member_add add_member
# Reomve a user to a group.
#
# @example
# Sonarqube.remove_member('AXQRcKrW9pRiZzanEJ2E', 'test-user')
# Sonarqube.remove_member('AXQRcKrW9pRiZzanEJ2E', 'test-user', {name: 'sonar-groups'})
#
# @param [String] id(required) The id of group.
# @param [String] login(required) The login of user.
# @param [Hash] options A customizable set of options.
# @option options [String] :name Optional name of group.
# @return [Sonarqube::ObjectifiedHash]
def remove_member(id = nil, login = nil, options = {})
raise ArgumentError, 'Missing required parameters' if id.nil? || login.nil?
post('/api/user_groups/remove_user', body: { id: id, login: login }.merge!(options))
end
alias member_remove remove_member
# List members of group.
#
# @example
# Sonarqube.list_members({id: 'AXQRcKrW9pRiZzanEJ2E'})
# Sonarqube.list_members({name: 'sonar-groups'})
#
# @param [Hash] options A customizable set of options.
# @option options [String] :name(required) Name of group.
# @option options [String] :id(required) Id of group.
# @return [Sonarqube::ObjectifiedHash] Information about added team member.
def list_members(options = {})
raise ArgumentError, 'Missing required parameters' if options[:id].nil? && options[:name].nil?
get('/api/user_groups/users', query: options)
end
alias members_list list_members
end
end

View File

@ -0,0 +1,111 @@
# frozen_string_literal: true
class Sonarqube::Client
# Defines methods related to projects.
# @see https://SONAR_URL/web_api/api/projects
module Projects
# Search for projects by name.
#
# @example
# Sonarqube.project_search()
# Sonarqube.project_search({ p: 2 })
# Sonarqube.search_projects({ ps: 42, p: 5 })
#
# @param [Hash] options A customizable set of options.
# @option options [String] :analyzedBefore Filter the projects for which last analysis is older than the given date
# @option options [Boolean] :onProvisionedOnly Filter the projects that are provisioned
# @option options [Integer] :ps Page size number of projects to return per page
# @option options [Integer] :p The page to retrieve
# @option options [String] :qualifiers Filter the results with the specified qualifiers (TRK,VW,APP)
# @option options [String] :q Limit search to component names that contain the supplied string or component keys that contain the supplied string
# @option options [String] :projects Comma-separated list of project keys
# (Any provided options will be passed to Sonarqube. See {https://SONAR_URL/web_api/api/projects/search}
# @return [Array<Sonarqube::ObjectifiedHash>]
def projects_search(options = {})
get('/api/projects/search', query: options)
end
alias search_projects projects_search
# Creates a new project.
#
# @example
# Sonarqube.create_project('sonarqube','sonarqube)
# Sonarqube.create_project('viking', 'ragnar' { visibility: 'private' })
#
# @param [String] name The name of a project.
# @param [String] key The key of a project.
# @param [Hash] options A customizable set of options.
# @option options [String] :visibility Visibility of a project (public or private).
# @return [Sonarqube::ObjectifiedHash] Information about created project.
def create_project(name, key = nil, options = {})
key = name if key.nil?
post('/api/projects/create', body: { name: name, project: key }.merge(options))
end
alias project_create create_project
# Deletes a project.
#
# @example
# Sonarqube.delete_project(4)
#
# @param [String] key The key of project.
# @return [Sonarqube::ObjectifiedHash] Information about deleted project.
def delete_project(key)
post('/api/projects/delete', body: { project: key })
end
alias project_delete delete_project
# Gets a list of project hooks.
#
# @example
# Sonarqube.project_update_key(42)
# Sonarqube.project_update_key('sonarqube')
#
# @param [String] key_ori The original key of a project.
# @param [String] key_new The New key of a project.
# @return [Array<Sonarqube::ObjectifiedHash>]
def project_update_key(key_ori, key_new)
post('/api/projects/update_key', body: { from: key_ori, to: key_new })
end
alias update_key_project project_update_key
# Gets a project hook.
#
# @example
# Sonarqube.project_hook(42, 5)
# Sonarqube.project_hook('sonarqube', 5)
#
# @param [String] project The name fo project.
# @param [String] visibility The visibility of a project.
# @return [Sonarqube::ObjectifiedHash]
def project_update_visibility(project, visibility)
post('/api/projects/update_visibility', body: { project: project, visibility: visibility })
end
alias update_visibility_project project_update_visibility
# Bulk delete projects.
#
# @example
# Sonarqube.project_bulk_delete()
# Sonarqube.project_bulk_delete({ p: 2 })
# Sonarqube.project_bulk_delete({ ps: 42, p: 5 })
#
# @param [Hash] options A customizable set of options.
# @option options [String] :analyzedBefore Filter the projects for which last analysis is older than the given date.
# @option options [Boolean] :onProvisionedOnly Filter the projects that are provisioned
# @option options [String] :qualifiers Filter the results with the specified qualifiers (TRK,VW,APP)
# @option options [String] :q Limit search to component names that contain the supplied string or component keys that contain the supplied string
# @option options [String] :projects Comma-separated list of project keys
# (Any provided options will be passed to Sonarqube. See {https://SONAR_URL/web_api/api/projects/bulk_delete}
# @return [Array<Sonarqube::ObjectifiedHash>]
def projects_bulk_delete(options = {})
if options[:analyzedBefore].nil? && options[:projects].nil? && options[:q].nil?
raise ArgumentError, 'Missing required parameters'
end
post('/api/projects/bulk_delete', body: options)
end
alias delete_bulk_projects projects_bulk_delete
end
end

View File

@ -0,0 +1,125 @@
# frozen_string_literal: true
class Sonarqube::Client
# Defines methods related to users.
# @see https://SONAR_URL/web_api/api/users
module Users
# Gets a list of users.
#
# @example
# Sonarqube.users_search()
# Sonarqube.users_search(query: { p: 1, ps: 10 })
# Sonarqube.users_search(query: { p: 1, ps: 10, q: 'sonarqube' })
#
# @param [Hash] options A customizable set of options.
# @option options [Integer] :ps Page size number of users to return per page
# @option options [Integer] :p The page to retrieve
# @option options [String] :q Filter on login, name and email
# @return [Array<Sonarqube::ObjectifiedHash>]
def users_search(options = {})
get('/api/users/search', query: options)
end
alias search_users users_search
# Creates a new user.
# Requires authentication from an admin account.
#
# @example
# Sonarqube.create_user('joe', 'joe', 'secret', , { mail: 'joe@foo.org' })
# or
# Sonarqube.create_user('joe', 'joe', 'secret')
#
# @param [String] name(required) The name of a user.
# @param [String] login(required) The login of a user.
# @param [String] password(required only is local user) The password of a user.
# @param [Hash] options A customizable set of options.
# @option options [String] :email The emails of a user.
# @option options [String] :local Specify if the user should be authenticated from SonarQube server or from an external authentication system. Password should not be set when local is set to false.
# @option options [String] :scmAccount List of SCM accounts. To set several values, the parameter must be called once for each value.
# @param [Hash] options A customizable set of options.
# @return [Sonarqube::ObjectifiedHash] Information about created user.
def create_user(login, name, password = nil, options = {})
body = { login: login, password: password, name: name }
body.merge!(options)
post('/api/users/create', body: body)
end
alias user_create create_user
# Updates a user.
#
# @example
# Sonarqube.update_user('joe', { email: 'joe.smith@foo.org', name: 'Joe' })
#
# @param [String] login(required) The login of a user
# @param [Hash] options A customizable set of options.
# @option options [String] :email The email of a user.
# @option options [String] :name The name of a user.
# @option options [String] :scmAccount SCM accounts. To set several values, the parameter must be called once for each value.
# @param [Hash] options A customizable set of options.
# @return [Sonarqube::ObjectifiedHash] Information about update user.
def update_user(login, options = {})
post('/api/users/update', body: { login: login }.merge!(options))
end
alias user_update update_user
# Blocks the specified user. Available only for admin.
#
# @example
# Sonarqube.block_user(15)
#
# @param [String] login(required) The login of a user
# @param [Hash] options A customizable set of options.
def deactivate_user(login, options = {})
post('/api/users/deactivate', body: { login: login }.merge!(options))
end
alias user_deactivate deactivate_user
# Change password the specified user. Available only for admin.
#
# @example
# Sonarqube.change_password_user('joe', 'password')
# Sonarqube.change_password_user('admin', 'password', admin)
#
# @param [String] login(required) The login of a user
# @param [String] password(required) New password for login
# @param [String] old_password(optional) Previous password. Required when changing one's own password.
# @param [Hash] options A customizable set of options.
def change_password_user(login, password, old_password = nil, options = {})
body = { login: login, password: password }
body = { old_password: old_password }.merge!(body) unless old_password.nil?
post('/api/users/change_password', body: body.merge!(options))
end
alias user_change_password change_password_user
# Creates a new user session.
#
# @example
# Sonarqube.session('jack@example.com', 'secret12345')
#
# @param [String] login(required) The login of a user
# @param [String] new_login(required) The new login of a user
# @param [Hash] options A customizable set of options.
# @return [Sonarqube::ObjectifiedHash]
def update_login_user(login, new_login, options = {})
post('/api/users/update_login', body: { login: login, newLogin: new_login }.merge!(options))
end
alias user_update_login update_login_user
# Lists the groups a user belongs to.
#
# @example
# Sonarqube.group_user
#
# @param [String] login A customizable set of options.
# @param [Hash] options A customizable set of options.
# @option options [Integer] :page The page number.
# @option options [Integer] :per_page The number of results per page.
# @option options [String] :from The start date for paginated results.
# @return [Array<Sonarqube::ObjectifiedHash>]
def groups_user(login, options = {})
get('/api/users/groups', query: { login: login }.merge!(options))
end
alias user_groups groups_user
end
end

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
module Sonarqube
# Defines constants and methods related to configuration.
module Configuration
# An array of valid keys in the options hash when configuring a Sonarqube::API.
VALID_OPTIONS_KEYS = %i[endpoint private_token user_agent httparty].freeze
# The user agent that will be sent to the API endpoint if none is set.
DEFAULT_USER_AGENT = "Sonarqube Ruby Gem #{Sonarqube::VERSION}"
# @private
attr_accessor(*VALID_OPTIONS_KEYS)
# @private
alias auth_token= private_token=
# Sets all configuration options to their default values
# when this module is extended.
def self.extended(base)
base.reset
end
# Convenience method to allow configuration options to be set in a block.
def configure
yield self
end
# Creates a hash of options and their values.
def options
VALID_OPTIONS_KEYS.inject({}) do |option, key|
option.merge!(key => send(key))
end
end
# Resets all configuration options to the defaults.
def reset
self.endpoint = ENV['SONARQUBE_API_ENDPOINT']
self.private_token = ENV['SONARQUBE_API_PRIVATE_TOKEN']
self.httparty = get_httparty_config(ENV['SONARQUBE_API_HTTPARTY_OPTIONS'])
self.user_agent = DEFAULT_USER_AGENT
end
private
# Allows HTTParty config to be specified in ENV using YAML hash.
def get_httparty_config(options)
return if options.nil?
httparty = Sonarqube::CLI::Helpers.yaml_load(options)
raise ArgumentError, 'HTTParty config should be a Hash.' unless httparty.is_a? Hash
Sonarqube::CLI::Helpers.symbolize_keys httparty
end
end
end

154
lib/sonarqube/error.rb Normal file
View File

@ -0,0 +1,154 @@
# frozen_string_literal: true
module Sonarqube
module Error
# Custom error class for rescuing from all Sonarqube errors.
class Error < StandardError; end
# Raised when API endpoint credentials not configured.
class MissingCredentials < Error; end
# Raised when impossible to parse response body.
class Parsing < Error; end
# Custom error class for rescuing from HTTP response errors.
class ResponseError < Error
POSSIBLE_MESSAGE_KEYS = %i[message error_description error].freeze
def initialize(response)
@response = response
super(build_error_message)
end
# Status code returned in the HTTP response.
#
# @return [Integer]
def response_status
@response.code
end
# Body content returned in the HTTP response
#
# @return [String]
def response_message
@response.parsed_response.message
end
# Additional error context returned by some API endpoints
#
# @return [String]
def error_code
if @response.respond_to?(:error_code)
@response.error_code
else
''
end
end
private
# Human friendly message.
#
# @return [String]
def build_error_message
parsed_response = classified_response
message = check_error_keys(parsed_response)
"Server responded with code #{@response.code}, message: " \
"#{handle_message(message)}. " \
"Request URI: #{@response.request.base_uri}#{@response.request.path}"
end
# Error keys vary across the API, find the first key that the parsed_response
# object responds to and return that, otherwise return the original.
def check_error_keys(resp)
key = POSSIBLE_MESSAGE_KEYS.find { |k| resp.respond_to?(k) }
key ? resp.send(key) : resp
end
# Parse the body based on the classification of the body content type
#
# @return parsed response
def classified_response
if @response.respond_to?('headers')
@response.headers['content-type'] == 'text/plain' ? { message: @response.to_s } : @response.parsed_response
else
@response.parsed_response
end
rescue Sonarqube::Error::Parsing
# Return stringified response when receiving a
# parsing error to avoid obfuscation of the
# api error.
#
# note: The Sonarqube API does not always return valid
# JSON when there are errors.
@response.to_s
end
# Handle error response message in case of nested hashes
def handle_message(message)
case message
when Sonarqube::ObjectifiedHash
message.to_h.sort.map do |key, val|
"'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : [val].flatten).join(' ')}"
end.join(', ')
when Array
message.join(' ')
else
message
end
end
end
# Raised when API endpoint returns the HTTP status code 400.
class BadRequest < ResponseError; end
# Raised when API endpoint returns the HTTP status code 401.
class Unauthorized < ResponseError; end
# Raised when API endpoint returns the HTTP status code 403.
class Forbidden < ResponseError; end
# Raised when API endpoint returns the HTTP status code 404.
class NotFound < ResponseError; end
# Raised when API endpoint returns the HTTP status code 405.
class MethodNotAllowed < ResponseError; end
# Raised when API endpoint returns the HTTP status code 406.
class NotAcceptable < ResponseError; end
# Raised when API endpoint returns the HTTP status code 409.
class Conflict < ResponseError; end
# Raised when API endpoint returns the HTTP status code 422.
class Unprocessable < ResponseError; end
# Raised when API endpoint returns the HTTP status code 429.
class TooManyRequests < ResponseError; end
# Raised when API endpoint returns the HTTP status code 500.
class InternalServerError < ResponseError; end
# Raised when API endpoint returns the HTTP status code 502.
class BadGateway < ResponseError; end
# Raised when API endpoint returns the HTTP status code 503.
class ServiceUnavailable < ResponseError; end
# HTTP status codes mapped to error classes.
STATUS_MAPPINGS = {
400 => BadRequest,
401 => Unauthorized,
403 => Forbidden,
404 => NotFound,
405 => MethodNotAllowed,
406 => NotAcceptable,
409 => Conflict,
422 => Unprocessable,
429 => TooManyRequests,
500 => InternalServerError,
502 => BadGateway,
503 => ServiceUnavailable
}.freeze
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
module Sonarqube
# Converts hashes to the objects.
class ObjectifiedHash
# Creates a new ObjectifiedHash object.
def initialize(hash)
@hash = hash
@data = hash.each_with_object({}) do |(key, value), data|
value = self.class.new(value) if value.is_a? Hash
value = value.map { |v| v.is_a?(Hash) ? self.class.new(v) : v } if value.is_a? Array
data[key.to_s] = value
end
end
# @return [Hash] The original hash.
def to_hash
hash
end
alias to_h to_hash
# @return [String] Formatted string with the class name, object id and original hash.
def inspect
"#<#{self.class}:#{object_id} {hash: #{hash.inspect}}"
end
def [](key)
data[key]
end
private
attr_reader :hash, :data
# Respond to messages for which `self.data` has a key
def method_missing(method_name, *args, &block)
if data.key?(method_name.to_s)
data[method_name.to_s]
elsif data.respond_to?(method_name)
warn 'WARNING: Please convert ObjectifiedHash object to hash before calling Hash methods on it.'
data.send(method_name, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
end
end
end

92
lib/sonarqube/request.rb Normal file
View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
require 'httparty'
require 'json'
module Sonarqube
# @private
class Request
include HTTParty
format :json
headers 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded'
parser(proc { |body, _| parse(body) })
attr_accessor :private_token, :endpoint
# Converts the response body to an ObjectifiedHash.
def self.parse(body)
body = decode(body)
if body.is_a? Hash
ObjectifiedHash.new body
elsif body
true
elsif !body
false
elsif body.nil?
false
else
raise Error::Parsing, "Couldn't parse a response body"
end
end
# Decodes a JSON response into Ruby object.
def self.decode(response)
response ? JSON.load(response) : {}
rescue JSON::ParserError
raise Error::Parsing, 'The response is not a valid JSON'
end
%w[get post put delete].each do |method|
define_method method do |path, options = {}|
params = options.dup
httparty_config(params)
unless params[:unauthenticated]
params[:headers] ||= {}
params[:headers].merge!(authorization_header)
end
validate self.class.send(method, @endpoint + path, params)
end
end
# Checks the response code for common errors.
# Returns parsed response for successful requests.
def validate(response)
error_klass = Error::STATUS_MAPPINGS[response.code]
raise error_klass, response if error_klass
parsed = response.parsed_response
parsed.client = self if parsed.respond_to?(:client=)
parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
parsed
end
# Sets a base_uri and default_params for requests.
# @raise [Error::MissingCredentials] if endpoint not set.
def request_defaults
raise Error::MissingCredentials, 'Please set an endpoint to API' unless @endpoint
self.class.default_params
end
private
# Returns an Authorization header hash
#
# @raise [Error::MissingCredentials] if private_token and auth_token are not set.
def authorization_header
raise Error::MissingCredentials, 'Please provide a private_token for user' unless @private_token
{ 'Authorization' => "Basic #{Base64.encode64("#{private_token}:")}" }
end
# Set HTTParty configuration
# @see https://github.com/jnunemaker/httparty
def httparty_config(options)
options.merge!(httparty) if httparty
end
end
end

5
lib/sonarqube/version.rb Normal file
View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
module Sonarqube
VERSION = '1.0.0'
end

29
sonarqube.gemspec Normal file
View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'sonarqube/version'
Gem::Specification.new do |gem|
gem.name = 'sonarqube'
gem.version = Sonarqube::VERSION
gem.authors = ['Mariani Lucas']
gem.email = ['marianilucas@gmail.com']
gem.description = 'Ruby client for Sonarqube API'
gem.summary = 'A Ruby wrapper for the Sonarqube API'
gem.homepage = 'https://github.com/psyreactor/sonarqube-ruby'
gem.files = Dir['{lib}/**/*', 'LICENSE.txt', 'README.md', 'CHANGELOG.md']
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ['lib']
gem.license = 'BSD-2-Clause'
gem.required_ruby_version = '>= 2.5'
gem.add_runtime_dependency 'httparty', '~> 0.14', '>= 0.14.0'
gem.add_runtime_dependency 'terminal-table', '~> 1.5', '>= 1.5.1'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'rspec'
gem.add_development_dependency 'webmock'
end

1
spec/fixtures/group.json vendored Normal file
View File

@ -0,0 +1 @@
{"paging":{"pageIndex":1,"pageSize":100,"total":1},"groups":[{"uuid":"AXOt93S3gMZPhbn-E_O7","name":"sonar-users","description":"Any new users created will automatically join this group","membersCount":1,"default":true}]}

1
spec/fixtures/group_create.json vendored Normal file
View File

@ -0,0 +1 @@
{"group":{"uuid":"AXQRHewQ9pRiZzanEJ15","organization":"default-organization","name":"Sonarqube-Group","membersCount":0,"default":false}}

View File

@ -0,0 +1 @@
{"group":{"uuid":"AXQRJ0fk9pRiZzanEJ2D","organization":"default-organization","name":"Sonarqube-Group","description":"Sonarqube group users","membersCount":0,"default":false}}

1
spec/fixtures/group_delete.json vendored Normal file
View File

@ -0,0 +1 @@
{}

1
spec/fixtures/group_update.json vendored Normal file
View File

@ -0,0 +1 @@
{"group":{"uuid":"AXQRcKrW9pRiZzanEJ2E","organization":"default-organization","name":"Sonarqube-Group","description":"New description group","membersCount":0,"default":false}}

1
spec/fixtures/groups.json vendored Normal file
View File

@ -0,0 +1 @@
{"paging":{"pageIndex":1,"pageSize":100,"total":2},"groups":[{"uuid":"AXOt93S3gMZPhbn-E_O6","name":"sonar-administrators","description":"System administrators","membersCount":1,"default":false},{"uuid":"AXOt93S3gMZPhbn-E_O7","name":"sonar-users","description":"Any new users created will automatically join this group","membersCount":1,"default":true}]}

1
spec/fixtures/member_add.json vendored Normal file
View File

@ -0,0 +1 @@
{}

1
spec/fixtures/member_remove.json vendored Normal file
View File

@ -0,0 +1 @@
{}

1
spec/fixtures/members_list.json vendored Normal file
View File

@ -0,0 +1 @@
{"users":[{"login":"admin","name":"Administrator","selected":true},{"login":"test","name":"test","selected":true}],"p":1,"ps":25,"total":2}

1
spec/fixtures/project.json vendored Normal file
View File

@ -0,0 +1 @@
{"project":{"key":"new_key","name":"new_project","qualifier":"TRK","visibility":"private"}}

1
spec/fixtures/project_delete.json vendored Normal file
View File

@ -0,0 +1 @@
{}

1
spec/fixtures/project_update_key.json vendored Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

1
spec/fixtures/projects_search.json vendored Normal file
View File

@ -0,0 +1 @@
{"paging":{"pageIndex":1,"pageSize":100,"total":1},"components":[{"organization":"default-organization","key":"test","name":"test","qualifier":"TRK","visibility":"public"}]}

View File

@ -0,0 +1 @@
{}

1
spec/fixtures/user_create.json vendored Normal file
View File

@ -0,0 +1 @@
{"user":{"login":"test","name":"test QA","scmAccounts":[],"active":true,"local":true}}

1
spec/fixtures/user_deactivate.json vendored Normal file
View File

@ -0,0 +1 @@
{"user":{"login":"test","name":"test","active":false,"local":true,"externalIdentity":"test","externalProvider":"sonarqube","groups":[],"scmAccounts":[]}}

1
spec/fixtures/user_groups.json vendored Normal file
View File

@ -0,0 +1 @@
{"paging":{"pageIndex":1,"pageSize":25,"total":1},"groups":[{"id":"AXOt93S3gMZPhbn-E_O7","name":"sonar-users","description":"Any new users created will automatically join this group","selected":true,"default":true}]}

1
spec/fixtures/user_update.json vendored Normal file
View File

@ -0,0 +1 @@
{"user":{"login":"test","name":"test","email":"test@local.ad","active":true,"local":true,"externalIdentity":"test","externalProvider":"sonarqube","groups":[],"scmAccounts":[]}}

1
spec/fixtures/user_update_login.json vendored Normal file
View File

@ -0,0 +1 @@
{}

1
spec/fixtures/users.json vendored Normal file
View File

@ -0,0 +1 @@
{"paging":{"pageIndex":1,"pageSize":50,"total":2},"users":[{"login":"admin","name":"Administrator","active":true,"groups":["sonar-administrators","sonar-users"],"tokensCount":1,"local":true,"externalIdentity":"admin","externalProvider":"sonarqube","lastConnectionDate":"2020-08-22T19:03:05+0000"},{"login":"test","name":"test","active":true,"email":"test@local.ad","groups":["Sonarqube-Group","sonar-users"],"tokensCount":0,"local":true,"externalIdentity":"test","externalProvider":"sonarqube","avatar":"30733e027a560af76b5142c5f521fb41"}]}

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::API do
let(:default_headers) { subject.class.default_options[:headers] }
describe '.default_options[:headers]' do
it "has 'User-Agent'" do
expect(default_headers).to include('User-Agent' => Sonarqube::Configuration::DEFAULT_USER_AGENT)
end
end
end

View File

@ -0,0 +1,139 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::Client do
describe '.groups' do
before do
stub_get('/api/user_groups/search', 'group').with(query: { 'q' => 'sonar-users' })
@group = Sonarqube.search_groups({ 'q' => 'sonar-users' })
end
it 'gets the correct resource' do
expect(a_get('/api/user_groups/search')
.with(query: { 'q' => 'sonar-users' })).to have_been_made
end
end
describe '.create_group' do
context 'without description' do
before do
stub_post('/api/user_groups/create', 'group_create')
@group = Sonarqube.create_group('Sonarqube-Group')
end
it 'gets the correct resource' do
expect(a_post('/api/user_groups/create')
.with(body: { name: 'Sonarqube-Group' })).to have_been_made
end
it 'returns information about a created group' do
expect(@group.group.name).to eq('Sonarqube-Group')
end
end
context 'with description' do
before do
stub_post('/api/user_groups/create', 'group_create_with_description')
@group = Sonarqube.create_group('Sonarqube-Group', { description: 'Sonarqube group users' })
end
it 'gets the correct resource' do
expect(a_post('/api/user_groups/create')
.with(body: { name: 'Sonarqube-Group',
description: 'Sonarqube group users' })).to have_been_made
end
it 'returns information about a created group' do
expect(@group.group.name).to eq('Sonarqube-Group')
expect(@group.group.description).to eq('Sonarqube group users')
end
end
end
describe '.delete_group' do
before do
stub_post('/api/user_groups/delete', 'group_delete')
@group = Sonarqube.delete_group('Sonarqube-Group')
end
it 'gets the correct resource' do
expect(a_post('/api/user_groups/delete')
.with(body: { name: 'Sonarqube-Group' })).to have_been_made
end
it 'returns information about a deleted group' do
expect(@group.to_hash).to be_empty
end
end
describe '.update_group' do
before do
stub_post('/api/user_groups/update', 'group_update')
@group = Sonarqube.update_group('AXQRcKrW9pRiZzanEJ2E', { description: 'New description group' })
end
it 'posts to the correct resource' do
expect(a_post('/api/user_groups/update')
.with(body: { id: 'AXQRcKrW9pRiZzanEJ2E',
description: 'New description group' })).to have_been_made
end
it 'returns information about the group' do
expect(@group.group.description).to eq('New description group')
end
end
describe '.member_add' do
before do
stub_post('/api/user_groups/add_user', 'member_add')
@member = Sonarqube.member_add('AXQRcKrW9pRiZzanEJ2E', 'test-user')
end
it 'gets the correct resource' do
expect(a_post('/api/user_groups/add_user')
.with(body: { id: 'AXQRcKrW9pRiZzanEJ2E',
login: 'test-user' })).to have_been_made
end
it "returns information about a group's members" do
expect(@member).to be_a Sonarqube::ObjectifiedHash
expect(@member.to_hash).to be_empty
end
end
describe '.member_remove' do
before do
stub_post('/api/user_groups/remove_user', 'member_remove')
@member = Sonarqube.member_remove('AXQRcKrW9pRiZzanEJ2E', 'test-user')
end
it 'gets the correct resource' do
expect(a_post('/api/user_groups/remove_user')
.with(body: { id: 'AXQRcKrW9pRiZzanEJ2E',
login: 'test-user' })).to have_been_made
end
it 'returns information about a group member' do
expect(@member).to be_a Sonarqube::ObjectifiedHash
expect(@member.to_hash).to be_empty
end
end
describe '.members_list' do
before do
stub_get('/api/user_groups/users', 'members_list').with(query: { 'id' => 'AXQRcKrW9pRiZzanEJ2E' })
@member = Sonarqube.members_list({ id: 'AXQRcKrW9pRiZzanEJ2E' })
end
it 'gets the correct resource' do
expect(a_get('/api/user_groups/users')
.with(query: { 'id' => 'AXQRcKrW9pRiZzanEJ2E' })).to have_been_made
end
it 'returns information about the edited member' do
expect(@member).to be_a Sonarqube::ObjectifiedHash
expect(@member.users[0].name).to eq('Administrator')
end
end
end

View File

@ -0,0 +1,101 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::Client do
it { is_expected.to respond_to :search_projects }
describe '.project_search' do
before do
stub_get('/api/projects/search', 'projects_search')
@projects_search = Sonarqube.projects_search
end
it 'gets the correct resource' do
expect(a_get('/api/projects/search')).to have_been_made
end
it 'returns response of projects found' do
expect(@projects_search).to be_a Sonarqube::ObjectifiedHash
expect(@projects_search.components.first.name).to eq('test')
expect(@projects_search.components.first.organization).to eq('default-organization')
end
end
describe '.project_create' do
before do
stub_post('/api/projects/create', 'project')
@project = Sonarqube.project_create('new_project', 'new_key', { visibility: 'private' })
end
it 'gets the correct resource' do
expect(a_post('/api/projects/create')).to have_been_made
end
it 'returns information about a created project' do
expect(@project.project.name).to eq('new_project')
expect(@project.project.key).to eq('new_key')
expect(@project.project.visibility).to eq('private')
end
end
describe '.project_delete' do
before do
stub_post('/api/projects/delete', 'project_delete')
@project = Sonarqube.project_delete('Sonarqube')
end
it 'gets the correct resource' do
expect(a_post('/api/projects/delete')).to have_been_made
end
it 'returns information about a deleted project' do
expect(@project.to_hash).to be_empty
end
end
describe '.project_update_key' do
before do
stub_post('/api/projects/update_key', 'project_update_key')
@project = Sonarqube.project_update_key('old_key', 'new_key')
end
it 'posts to the correct resource' do
expect(a_post('/api/projects/update_key')).to have_been_made
end
it 'returns information about a update project key' do
expect(@project.to_hash).to be_empty
end
end
describe '.project_update_visibility' do
before do
stub_post('/api/projects/update_visibility', 'project_update_visibility')
@project_update_visibility = Sonarqube.project_update_visibility('new_project', 'public')
end
it 'gets the correct resource' do
expect(a_post('/api/projects/update_visibility')).to have_been_made
end
it 'returns a response of projects update visibility' do
expect(@project_update_visibility.to_hash).to be_empty
end
end
describe '.projects_bulk_delete' do
before do
stub_post('/api/projects/bulk_delete', 'projects_bulk_delete')
@projects_bulk_delete = Sonarqube.projects_bulk_delete({ projects: 'old_project' })
end
it 'gets the correct resource' do
expect(a_post('/api/projects/bulk_delete')).to have_been_made
end
it 'returns a response of projects bulk delete' do
expect(@projects_bulk_delete.to_hash).to be_empty
end
end
end

View File

@ -0,0 +1,114 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::Client do
describe '.users_search' do
before do
stub_get('/api/users/search', 'users')
@users = Sonarqube.users_search
end
it 'gets the correct resource' do
expect(a_get('/api/users/search')).to have_been_made
end
it 'returns a paginated response of users' do
expect(@users).to be_a Sonarqube::ObjectifiedHash
expect(@users.users.first.login).to eq('admin')
end
end
describe '.user_create' do
context 'when successful request' do
before do
stub_post('/api/users/create', 'user_create')
@user = Sonarqube.user_create('test', 'test QA', 'secretpassword')
end
it 'gets the correct resource' do
body = { login: 'test', password: 'secretpassword', name: 'test QA' }
expect(a_post('/api/users/create').with(body: body)).to have_been_made
end
it 'returns information about a created user' do
expect(@user.user.login).to eq('test')
end
end
end
describe '.user_update' do
before do
stub_post('/api/users/update', 'user_update').with(body: { login: 'test', description: 'Test New QA' })
@user = Sonarqube.user_update('test', { description: 'Test New QA' })
end
it 'gets the correct resource' do
expect(a_post('/api/users/update').with(body: { login: 'test', description: 'Test New QA' })).to have_been_made
end
it 'returns information about a update user' do
expect(@user.user.login).to eq('test')
end
end
describe '.user_deactivate' do
before do
stub_post('/api/users/deactivate', 'user_deactivate')
@user = Sonarqube.user_deactivate('test')
end
it 'gets the correct resource' do
expect(a_post('/api/users/deactivate')).to have_been_made
end
it 'returns information about a deactivate user' do
expect(@user.user.login).to eq('test')
end
end
describe '.user_change_password' do
before do
stub_post('/api/users/change_password', 'user_change_password')
@result = Sonarqube.user_change_password('test', 'newpassword')
end
it 'gets the correct resource' do
expect(a_post('/api/users/change_password')).to have_been_made
end
it 'returns information about a change user password' do
expect(@result.to_hash).to be_empty
end
end
describe '.user_update_login' do
before do
stub_post('/api/users/update_login', 'user_update_login')
@result = Sonarqube.user_update_login('test', 'newlogin')
end
it 'gets the correct resource' do
expect(a_post('/api/users/update_login')).to have_been_made
end
it 'returns information about a change user login' do
expect(@result.to_hash).to be_empty
end
end
describe '.user_groups' do
before do
stub_get('/api/users/groups', 'user_groups').with(query: { login: 'test' })
@result = Sonarqube.user_groups('test')
end
it 'gets the correct resource' do
expect(a_get('/api/users/groups').with(query: { login: 'test' })).to have_been_made
end
it 'returns boolean' do
expect(@result.groups.first.name).to eq('sonar-users')
end
end
end

View File

@ -0,0 +1,106 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::Error::ResponseError do
before do
@request_double = double(base_uri: 'https://sonarqube.com/api/v3', path: '/foo')
end
let(:expected_messages) do
[
%r{Server responded with code \d+, message: Displayed message. Request URI: https://sonarqube.com/api/v3/foo},
%r{Server responded with code \d+, message: Displayed error_description. Request URI: https://sonarqube.com/api/v3/foo},
%r{Server responded with code \d+, message: Displayed error. Request URI: https://sonarqube.com/api/v3/foo},
%r{Server responded with code \d+, message: 'embed_entity' \(foo: bar\) \(sna: fu\), 'password' too short. Request URI: https://sonarqube.com/api/v3/foo},
%r{Server responded with code \d+, message: First message. Second message.. Request URI: https://sonarqube.com/api/v3/foo},
%r{Server responded with code \d+, message: 'error' Spam detected. Request URI: https://sonarqube.com/api/v3/foo}
]
end
# Set up some response scenarios to test.
[
{ code: 401, parsed_response: Sonarqube::ObjectifiedHash.new(message: 'Displayed message', error_description: 'should not be displayed', error: 'also will not be displayed') },
{ code: 404, parsed_response: Sonarqube::ObjectifiedHash.new(error_description: 'Displayed error_description', error: 'also will not be displayed') },
{ code: 401, parsed_response: Sonarqube::ObjectifiedHash.new(error: 'Displayed error') },
{ code: 500, parsed_response: Sonarqube::ObjectifiedHash.new(embed_entity: { foo: ['bar'], sna: ['fu'] }, password: ['too short']) },
{ code: 403, parsed_response: Array.new(['First message.', 'Second message.']) },
{ code: 400, parsed_response: Sonarqube::ObjectifiedHash.new(message: { error: 'Spam detected' }) }
].each_with_index do |data, index|
it 'returns the expected message' do
response_double = double(**data, request: @request_double)
expect(described_class.new(response_double).message).to match expected_messages[index]
end
end
end
describe Sonarqube::Error::ResponseError do
before do
@request_double = double('request', base_uri: 'https://sonarqube.com/api/v3', path: '/foo', options: {})
end
it 'Builds an error message from text' do
headers = { 'content-type' => 'text/plain' }
response_double = double('response', body: 'Retry later', to_s: 'Retry text', parsed_response: { message: 'Retry hash' }, code: 429, options: {}, headers: headers, request: @request_double)
expect(described_class.new(response_double).send(:build_error_message)).to match(/Retry text/)
end
it 'Builds an error message from parsed json' do
headers = { 'content-type' => 'application/json' }
response_double = double('response', body: 'Retry later', to_s: 'Retry text', parsed_response: { message: 'Retry hash' }, code: 429, options: {}, headers: headers, request: @request_double)
expect(described_class.new(response_double).send(:build_error_message)).to match(/Retry hash/)
end
context 'parsing errors' do
let(:headers) { { 'content-type' => 'application/json' } }
let(:response_double) do
double('response', body: 'Retry later', to_s: 'Retry text', code: status, options: {}, headers: headers, request: @request_double)
end
let(:status) { 429 }
before do
allow(response_double).to receive(:parsed_response)
.and_raise(Sonarqube::Error::Parsing)
end
it 'Builds an error message from text' do
expect(described_class.new(response_double).send(:build_error_message)).to match(/Retry text/)
end
end
describe '#error_code' do
it 'returns the value when available' do
headers = { 'content-type' => 'application/json' }
response_double = double(
'response',
body: 'Retry later',
to_s: 'Retry text',
parsed_response: { message: 'Retry hash' },
code: 400,
error_code: 'conflict',
options: {},
headers: headers,
request: @request_double
)
expect(described_class.new(response_double).error_code).to eq 'conflict'
end
it 'returns nothing when unavailable' do
headers = { 'content-type' => 'application/json' }
response_double = double(
'response',
body: 'Retry later',
to_s: 'Retry text',
parsed_response: { message: 'Retry hash' },
code: 400,
options: {},
headers: headers,
request: @request_double
)
expect(described_class.new(response_double).error_code).to eq ''
end
end
end

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::ObjectifiedHash do
before do
@hash = { a: 1, b: 2, 'string' => 'string', symbol: :symbol, array: ['string', { a: 1, b: 2 }] }
@oh = described_class.new @hash
end
describe 'Hash behavior' do
let(:hash) { { foo: 'bar' } }
let(:oh) { described_class.new(hash) }
it 'allows to call Hash methods' do
expect(oh['foo']).to eq('bar')
expect(oh.merge(key: :value)).to eq({ 'foo' => 'bar', key: :value })
end
it 'warns about calling Hash methods' do
output = capture_output { oh.values }
expect(output).to eq("WARNING: Please convert ObjectifiedHash object to hash before calling Hash methods on it.\n")
end
end
it 'objectifies a hash' do
expect(@oh.a).to eq(@hash[:a])
expect(@oh.b).to eq(@hash[:b])
end
it 'objectifies a hash contained in an array' do
expect(@oh.array[1].a).to eq(@hash[:array][1][:a])
expect(@oh.array[1].b).to eq(@hash[:array][1][:b])
expect(@oh.array[0]).to eq(@hash[:array][0])
end
it 'supports legacy addressing mode' do
expect(@oh['a']).to eq(@hash[:a])
expect(@oh['b']).to eq(@hash[:b])
end
describe '#to_hash' do
it 'returns an original hash' do
expect(@oh.to_hash).to eq(@hash)
end
it 'has an alias #to_h' do
expect(@oh).to respond_to(:to_h)
end
end
describe '#inspect' do
it 'returns a formatted string' do
pretty_string = "#<#{@oh.class.name}:#{@oh.object_id} {hash: #{@hash}}"
expect(@oh.inspect).to eq(pretty_string)
end
end
describe '#respond_to' do
it 'returns true for methods this object responds to through method_missing as sym' do
expect(@oh).to respond_to(:a)
end
it 'returns true for methods this object responds to through method_missing as string' do
expect(@oh).to respond_to('string')
end
it 'does not care if you use a string or symbol to reference a method' do
expect(@oh).to respond_to(:string)
end
it 'does not care if you use a string or symbol to reference a method' do
expect(@oh).to respond_to('symbol')
end
end
end

View File

@ -0,0 +1,99 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube::Request do
before do
# Prevent tests modifying the `default_params` value from causing cross-test
# pollution
@request = described_class.new
end
it { is_expected.to respond_to :get }
it { is_expected.to respond_to :post }
it { is_expected.to respond_to :put }
it { is_expected.to respond_to :delete }
describe '.default_options' do
it 'has default values' do
default_options = described_class.default_options
expect(default_options).to be_a Hash
expect(default_options[:parser]).to be_a Proc
expect(default_options[:format]).to eq(:json)
expect(default_options[:headers]).to eq('Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded')
end
end
describe '.parse' do
it 'returns ObjectifiedHash' do
body = JSON.unparse(a: 1, b: 2)
expect(described_class.parse(body)).to be_an Sonarqube::ObjectifiedHash
expect(described_class.parse('true')).to be true
expect(described_class.parse('false')).to be false
expect { described_class.parse('string') }.to raise_error(Sonarqube::Error::Parsing)
end
end
describe '#request_defaults' do
context 'when endpoint is not set' do
it 'raises Error::MissingCredentials' do
@request.endpoint = nil
expect do
@request.request_defaults
end.to raise_error(Sonarqube::Error::MissingCredentials, 'Please set an endpoint to API')
end
end
end
describe 'HTTP request methods' do
it 'does not overwrite headers set via HTTParty configuration' do
@request.private_token = 'token'
@request.endpoint = 'https://example.com'
path = "#{@request.endpoint}/version"
# Stub Sonarqube::Configuration
allow(@request).to receive(:httparty).and_return(
headers: { 'Cookie' => 'sonarqube_canary=true' }
)
stub_request(:get, path)
@request.get('/version')
expect(a_request(:get, path).with(headers: {
'Authorization' => "Basic #{Base64.strict_encode64('token:')}",
'Cookie' => 'sonarqube_canary=true'
}.merge(described_class.headers))).to have_been_made
end
it 'does not modify options in-place' do
options = { per_page: 10 }
original_options = options.dup
@request.private_token = 'token'
@request.endpoint = 'https://example.com'
# Stub Sonarqube::Configuration
allow(@request).to receive(:httparty).and_return(nil)
stub_request(:get, "#{@request.endpoint}/projects_search")
@request.get('/projects_search', options)
expect(options).to eq(original_options)
end
end
describe '#authorization_header' do
it 'raises MissingCredentials when auth_token and private_token are not set' do
expect do
@request.send(:authorization_header)
end.to raise_error(Sonarqube::Error::MissingCredentials)
end
it 'sets the correct header when given a private_token' do
@request.private_token = 'sjkahwu9827u2jw'
expect(@request.send(:authorization_header)).to eq('Authorization' => "Basic #{Base64.encode64("#{@request.private_token}:")}")
end
end
end

91
spec/sonarqube_spec.rb Normal file
View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
require 'spec_helper'
describe Sonarqube do
after { described_class.reset }
describe '.client' do
it 'is a Sonarqube::Client' do
expect(described_class.client).to be_a Sonarqube::Client
end
it 'does not override each other' do
client1 = described_class.client(endpoint: 'https://api1.example.com', private_token: '001')
client2 = described_class.client(endpoint: 'https://api2.example.com', private_token: '002')
expect(client1.endpoint).to eq('https://api1.example.com')
expect(client2.endpoint).to eq('https://api2.example.com')
expect(client1.private_token).to eq('001')
expect(client2.private_token).to eq('002')
end
it 'sets private_token to the auth_token when provided' do
client = described_class.client(endpoint: 'https://api2.example.com', auth_token: '3225e2804d31fea13fc41fc83bffef00cfaedc463118646b154acc6f94747603')
expect(client.private_token).to eq('3225e2804d31fea13fc41fc83bffef00cfaedc463118646b154acc6f94747603')
end
end
describe '.actions' do
it 'returns an array of client methods' do
actions = described_class.actions
expect(actions).to be_an Array
expect(actions.first).to be_a Symbol
expect(actions.min).to eq(:add_member)
end
end
describe '.endpoint=' do
it 'sets endpoint' do
described_class.endpoint = 'https://api.example.com'
expect(described_class.endpoint).to eq('https://api.example.com')
end
end
describe '.private_token=' do
it 'sets private_token' do
described_class.private_token = 'secret'
expect(described_class.private_token).to eq('secret')
end
end
describe '.auth_token=' do
it 'sets auth_token', focus: true do
described_class.auth_token = 'auth_secret'
expect(described_class.private_token).to eq('auth_secret')
end
end
describe '.user_agent' do
it 'returns default user_agent' do
expect(described_class.user_agent).to eq(Sonarqube::Configuration::DEFAULT_USER_AGENT)
end
end
describe '.user_agent=' do
it 'sets user_agent' do
described_class.user_agent = 'Custom User Agent'
expect(described_class.user_agent).to eq('Custom User Agent')
end
end
describe '.configure' do
Sonarqube::Configuration::VALID_OPTIONS_KEYS.each do |key|
it "sets #{key}" do
described_class.configure do |config|
config.send("#{key}=", key)
expect(described_class.send(key)).to eq(key)
end
end
end
end
describe '.http_proxy' do
it 'delegates the method to Sonarqube::Request' do
described_class.endpoint = 'https://api.example.com'
request = class_spy(Sonarqube::Request).as_stubbed_const
described_class.http_proxy('proxy.example.net', 1987, 'user', 'pass')
expect(request).to have_received(:http_proxy).with('proxy.example.net', 1987, 'user', 'pass')
end
end
end

41
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'rspec'
require 'webmock/rspec'
require File.expand_path('../lib/sonarqube', __dir__)
def capture_output
out = StringIO.new
$stdout = out
$stderr = out
yield
$stdout = STDOUT
$stderr = STDERR
out.string
end
def load_fixture(name)
name, extension = name.split('.')
File.new(File.dirname(__FILE__) + "/fixtures/#{name}.#{extension || 'json'}")
end
RSpec.configure do |config|
config.before(:all) do
Sonarqube.endpoint = 'https://api.example.com'
Sonarqube.private_token = 'secret'
end
end
%i[get post put delete].each do |method|
define_method "stub_#{method}" do |path, fixture, status_code = 200|
stub_request(method, "#{Sonarqube.endpoint}#{path}")
.with(headers: { 'Authorization' => "Basic #{Base64.strict_encode64("#{Sonarqube.private_token}:")}" })
.to_return(body: load_fixture(fixture), status: status_code)
end
define_method "a_#{method}" do |path|
a_request(method, "#{Sonarqube.endpoint}#{path}")
.with(headers: { 'Authorization' => "Basic #{Base64.strict_encode64("#{Sonarqube.private_token}:")}" })
end
end