diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2014-02-18 14:15:40 +0000 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2014-02-18 14:15:40 +0000 |
commit | 8686d09d3e9e53089f7d3f11d74f0368f76a2aa2 (patch) | |
tree | 9702483393e25eabd403b98d3af2de0a0e0cc75a | |
parent | 8a55636f8974d73c097a52721e2208cea727bc17 (diff) | |
parent | 6cf39fe10ddf6f90a17d52ba6b50425f58215eeb (diff) | |
download | gitlab-ce-8686d09d3e9e53089f7d3f11d74f0368f76a2aa2.tar.gz |
Merge branch 'feature/api_fle_encoded' into 'master'
Complete api files CRUD
Adds ability to read file from repository with content encoded with Base64.
-rw-r--r-- | app/models/repository.rb | 12 | ||||
-rw-r--r-- | app/views/help/_api_layout.html.haml | 2 | ||||
-rw-r--r-- | doc/api/README.md | 2 | ||||
-rw-r--r-- | doc/api/commits.md | 95 | ||||
-rw-r--r-- | doc/api/repositories.md | 134 | ||||
-rw-r--r-- | doc/api/repository_files.md | 102 | ||||
-rw-r--r-- | lib/api/api.rb | 1 | ||||
-rw-r--r-- | lib/api/commits.rb | 64 | ||||
-rw-r--r-- | lib/api/files.rb | 53 | ||||
-rw-r--r-- | lib/api/repositories.rb | 44 | ||||
-rw-r--r-- | spec/requests/api/commits_spec.rb | 87 | ||||
-rw-r--r-- | spec/requests/api/files_spec.rb | 30 | ||||
-rw-r--r-- | spec/requests/api/repositories_spec.rb | 71 |
13 files changed, 440 insertions, 257 deletions
diff --git a/app/models/repository.rb b/app/models/repository.rb index 2c2bf242b94..99d908b5d81 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -180,13 +180,13 @@ class Repository end def blob_at_branch(branch_name, path) - last_commit = commit(branch_name) + last_commit = commit(branch_name) - if last_commit - blob_at(last_commit.sha, path) - else - nil - end + if last_commit + blob_at(last_commit.sha, path) + else + nil + end end # Returns url for submodule diff --git a/app/views/help/_api_layout.html.haml b/app/views/help/_api_layout.html.haml index c211b658410..af723f906d9 100644 --- a/app/views/help/_api_layout.html.haml +++ b/app/views/help/_api_layout.html.haml @@ -5,7 +5,7 @@ %i.icon-angle-left Back to help %ul.nav.nav-pills.nav-stacked - - %w(README projects project_snippets repositories deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file| + - %w(README projects project_snippets repositories repository_files commits deploy_keys users groups session issues milestones merge_requests notes system_hooks).each do |file| %li{class: file == @category ? 'active' : nil} = link_to file.titleize, help_api_file_path(file) diff --git a/doc/api/README.md b/doc/api/README.md index 517a9fae6f6..850666953a3 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -127,6 +127,8 @@ But when you want to create a link to web page - use `http:://host/project/issu + [Projects](projects.md) + [Project Snippets](project_snippets.md) + [Repositories](repositories.md) ++ [Repository Files](repository_files.md) ++ [Commits](commits.md) + [Merge Requests](merge_requests.md) + [Issues](issues.md) + [Milestones](milestones.md) diff --git a/doc/api/commits.md b/doc/api/commits.md new file mode 100644 index 00000000000..69b44a2e83b --- /dev/null +++ b/doc/api/commits.md @@ -0,0 +1,95 @@ +# Commits API + +## List repository commits + +Get a list of repository commits in a project. + +``` +GET /projects/:id/repository/commits +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch + +```json +[ + { + "id": "ed899a2f4b50b4370feeea94676502b42383c746", + "short_id": "ed899a2f4b5", + "title": "Replace sanitize with escape once", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dzaporozhets@sphereconsultinginc.com", + "created_at": "2012-09-20T11:50:22+03:00" + }, + { + "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", + "short_id": "6104942438c", + "title": "Sanitize for network graph", + "author_name": "randx", + "author_email": "dmitriy.zaporozhets@gmail.com", + "created_at": "2012-09-20T09:06:12+03:00" + } +] +``` + +## Get a single commit + +Get a specific commit identified by the commit hash or name of a branch or tag. + +``` +GET /projects/:id/repository/commits/:sha +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `sha` (required) - The commit hash or name of a repository branch or tag + +```json +{ + "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", + "short_id": "6104942438c", + "title": "Sanitize for network graph", + "author_name": "randx", + "author_email": "dmitriy.zaporozhets@gmail.com", + "created_at": "2012-09-20T09:06:12+03:00", + "committed_date": "2012-09-20T09:06:12+03:00", + "authored_date": "2012-09-20T09:06:12+03:00", + "parent_ids" : [ + "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" + ] +} +``` + + +## Get the diff of a commit + +Get the diff of a commit in a project. + +``` +GET /projects/:id/repository/commits/:sha/diff +``` + +Parameters: + ++ `id` (required) - The ID of a project ++ `sha` (required) - The name of a repository branch or tag or if not given the default branch + +```json +[ + { + "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", + "new_path": "doc/update/5.4-to-6.0.md", + "old_path": "doc/update/5.4-to-6.0.md", + "a_mode": null, + "b_mode": "100644", + "new_file": false, + "renamed_file": false, + "deleted_file": false + } +] +``` + + diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 01607263008..65ea3615354 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -204,99 +204,6 @@ Parameters: ] ``` - -## List repository commits - -Get a list of repository commits in a project. - -``` -GET /projects/:id/repository/commits -``` - -Parameters: - -+ `id` (required) - The ID of a project -+ `ref_name` (optional) - The name of a repository branch or tag or if not given the default branch - -```json -[ - { - "id": "ed899a2f4b50b4370feeea94676502b42383c746", - "short_id": "ed899a2f4b5", - "title": "Replace sanitize with escape once", - "author_name": "Dmitriy Zaporozhets", - "author_email": "dzaporozhets@sphereconsultinginc.com", - "created_at": "2012-09-20T11:50:22+03:00" - }, - { - "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", - "short_id": "6104942438c", - "title": "Sanitize for network graph", - "author_name": "randx", - "author_email": "dmitriy.zaporozhets@gmail.com", - "created_at": "2012-09-20T09:06:12+03:00" - } -] -``` - -## Get a single commit - -Get a specific commit identified by the commit hash or name of a branch or tag. - -``` -GET /projects/:id/repository/commits/:sha -``` - -Parameters: - -+ `id` (required) - The ID of a project -+ `sha` (required) - The commit hash or name of a repository branch or tag - -```json -{ - "id": "6104942438c14ec7bd21c6cd5bd995272b3faff6", - "short_id": "6104942438c", - "title": "Sanitize for network graph", - "author_name": "randx", - "author_email": "dmitriy.zaporozhets@gmail.com", - "created_at": "2012-09-20T09:06:12+03:00", - "committed_date": "2012-09-20T09:06:12+03:00", - "authored_date": "2012-09-20T09:06:12+03:00", - "parent_ids" : [ - "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" - ] -} -``` - - -## Get the diff of a commit - -Get the diff of a commit in a project. - -``` -GET /projects/:id/repository/commits/:sha/diff -``` - -Parameters: - -+ `id` (required) - The ID of a project -+ `sha` (required) - The name of a repository branch or tag or if not given the default branch - -```json -[ - { - "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", - "new_path": "doc/update/5.4-to-6.0.md", - "old_path": "doc/update/5.4-to-6.0.md", - "a_mode": null, - "b_mode": "100644", - "new_file": false, - "renamed_file": false, - "deleted_file": false - } -] -``` - ## List repository tree Get a list of repository files and directories in a project. @@ -388,44 +295,3 @@ GET /projects/:id/repository/archive Parameters: + `id` (required) - The ID of a project + `sha` (optional) - The commit sha to download defaults to the tip of the default branch - - -## Create new file in repository - -``` -POST /projects/:id/repository/files -``` - -Parameters: - -+ `file_path` (optional) - Full path to new file. Ex. lib/class.rb -+ `branch_name` (required) - The name of branch -+ `encoding` (optional) - 'text' or 'base64'. Text is default. -+ `content` (required) - File content -+ `commit_message` (required) - Commit message - -## Update existing file in repository - -``` -PUT /projects/:id/repository/files -``` - -Parameters: - -+ `file_path` (required) - Full path to file. Ex. lib/class.rb -+ `branch_name` (required) - The name of branch -+ `encoding` (optional) - 'text' or 'base64'. Text is default. -+ `content` (required) - New file content -+ `commit_message` (required) - Commit message - -## Delete existing file in repository - -``` -DELETE /projects/:id/repository/files -``` - -Parameters: - -+ `file_path` (required) - Full path to file. Ex. lib/class.rb -+ `branch_name` (required) - The name of branch -+ `commit_message` (required) - Commit message diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md new file mode 100644 index 00000000000..cafab8c828f --- /dev/null +++ b/doc/api/repository_files.md @@ -0,0 +1,102 @@ +# CRUD for repository files + +## Create, read, update and delete repository files using this API + +- - - + +## Get file from repository + +Allows you to receive information about file in repository like name, size, content. +Note that file content is Base64 encoded. + +``` +GET /projects/:id/repository/files +``` + +Example response: + +```json +{ + "file_name": "key.rb", + "file_path": "app/models/key.rb", + "size": 1476, + "encoding": "base64", + "content": "IyA9PSBTY2hlbWEgSW5mb3...", + "ref": "master", + "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", + "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50" +} +``` + +Parameters: + ++ `file_path` (required) - Full path to new file. Ex. lib/class.rb ++ `ref` (required) - The name of branch, tag or commit + +## Create new file in repository + +``` +POST /projects/:id/repository/files +``` + +Example response: + +```json +{ + "file_name": "app/project.rb", + "branch_name": "master", +} +``` + +Parameters: + ++ `file_path` (required) - Full path to new file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `encoding` (optional) - 'text' or 'base64'. Text is default. ++ `content` (required) - File content ++ `commit_message` (required) - Commit message + +## Update existing file in repository + +``` +PUT /projects/:id/repository/files +``` + +Example response: + +```json +{ + "file_name": "app/project.rb", + "branch_name": "master", +} +``` + +Parameters: + ++ `file_path` (required) - Full path to file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `encoding` (optional) - 'text' or 'base64'. Text is default. ++ `content` (required) - New file content ++ `commit_message` (required) - Commit message + +## Delete existing file in repository + +``` +DELETE /projects/:id/repository/files +``` + +Example response: + +```json +{ + "file_name": "app/project.rb", + "branch_name": "master", +} +``` + +Parameters: + ++ `file_path` (required) - Full path to file. Ex. lib/class.rb ++ `branch_name` (required) - The name of branch ++ `commit_message` (required) - Commit message + diff --git a/lib/api/api.rb b/lib/api/api.rb index 9022cf7375a..6bec8368b12 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -42,6 +42,7 @@ module API mount ProjectHooks mount Services mount Files + mount Commits mount Namespaces end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb new file mode 100644 index 00000000000..33b8b3d2244 --- /dev/null +++ b/lib/api/commits.rb @@ -0,0 +1,64 @@ +require 'mime/types' + +module API + # Projects API + class Commits < Grape::API + before { authenticate! } + before { authorize! :download_code, user_project } + + resource :projects do + helpers do + def handle_project_member_errors(errors) + if errors[:project_access].any? + error!(errors[:project_access], 422) + end + not_found! + end + end + + # Get a project repository commits + # + # Parameters: + # id (required) - The ID of a project + # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used + # Example Request: + # GET /projects/:id/repository/commits + get ":id/repository/commits" do + page = (params[:page] || 0).to_i + per_page = (params[:per_page] || 20).to_i + ref = params[:ref_name] || user_project.try(:default_branch) || 'master' + + commits = user_project.repository.commits(ref, nil, per_page, page * per_page) + present commits, with: Entities::RepoCommit + end + + # Get a specific commit of a project + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash or name of a repository branch or tag + # Example Request: + # GET /projects/:id/repository/commits/:sha + get ":id/repository/commits/:sha" do + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! "Commit" unless commit + present commit, with: Entities::RepoCommitDetail + end + + # Get the diff for a specific commit of a project + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit or branch name + # Example Request: + # GET /projects/:id/repository/commits/:sha/diff + get ":id/repository/commits/:sha/diff" do + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! "Commit" unless commit + commit.diffs + end + end + end +end diff --git a/lib/api/files.rb b/lib/api/files.rb index 213604915a6..e0c46f92b84 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -5,10 +5,61 @@ module API before { authorize! :push_code, user_project } resource :projects do + # Get file from repository + # File content is Base64 encoded + # + # Parameters: + # file_path (required) - The path to the file. Ex. lib/class.rb + # ref (required) - The name of branch, tag or commit + # + # Example Request: + # GET /projects/:id/repository/files + # + # Example response: + # { + # "file_name": "key.rb", + # "file_path": "app/models/key.rb", + # "size": 1476, + # "encoding": "base64", + # "content": "IyA9PSBTY2hlbWEgSW5mb3...", + # "ref": "master", + # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", + # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50" + # } + # + get ":id/repository/files" do + required_attributes! [:file_path, :ref] + attrs = attributes_for_keys [:file_path, :ref] + ref = attrs.delete(:ref) + file_path = attrs.delete(:file_path) + + commit = user_project.repository.commit(ref) + not_found! "Commit" unless commit + + blob = user_project.repository.blob_at(commit.sha, file_path) + + if blob + status(200) + + { + file_name: blob.name, + file_path: blob.path, + size: blob.size, + encoding: "base64", + content: Base64.encode64(blob.data), + ref: ref, + blob_id: blob.id, + commit_id: commit.id, + } + else + render_api_error!('File not found', 404) + end + end + # Create new file in repository # # Parameters: - # file_path (optional) - The path to new file. Ex. lib/class.rb + # file_path (required) - The path to new file. Ex. lib/class.rb # branch_name (required) - The name of branch # content (required) - File content # commit_message (required) - Commit message diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index c305c889fc2..ba53bf9baa4 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -85,50 +85,6 @@ module API present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project end - # Get a project repository commits - # - # Parameters: - # id (required) - The ID of a project - # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used - # Example Request: - # GET /projects/:id/repository/commits - get ":id/repository/commits" do - page = (params[:page] || 0).to_i - per_page = (params[:per_page] || 20).to_i - ref = params[:ref_name] || user_project.try(:default_branch) || 'master' - - commits = user_project.repository.commits(ref, nil, per_page, page * per_page) - present commits, with: Entities::RepoCommit - end - - # Get a specific commit of a project - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash or name of a repository branch or tag - # Example Request: - # GET /projects/:id/repository/commits/:sha - get ":id/repository/commits/:sha" do - sha = params[:sha] - commit = user_project.repository.commit(sha) - not_found! "Commit" unless commit - present commit, with: Entities::RepoCommitDetail - end - - # Get the diff for a specific commit of a project - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit or branch name - # Example Request: - # GET /projects/:id/repository/commits/:sha/diff - get ":id/repository/commits/:sha/diff" do - sha = params[:sha] - commit = user_project.repository.commit(sha) - not_found! "Commit" unless commit - commit.diffs - end - # Get a project repository tree # # Parameters: diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb new file mode 100644 index 00000000000..ea317e1137a --- /dev/null +++ b/spec/requests/api/commits_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' +require 'mime/types' + +describe API::API do + include ApiHelpers + before(:each) { enable_observers } + after(:each) {disable_observers} + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } + let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } + + before { project.team << [user, :reporter] } + + describe "GET /projects/:id/repository/commits" do + context "authorized user" do + before { project.team << [user2, :reporter] } + + it "should return project commits" do + get api("/projects/#{project.id}/repository/commits", user) + response.status.should == 200 + + json_response.should be_an Array + json_response.first['id'].should == project.repository.commit.id + end + end + + context "unauthorized user" do + it "should not return project commits" do + get api("/projects/#{project.id}/repository/commits") + response.status.should == 401 + end + end + end + + describe "GET /projects:id/repository/commits/:sha" do + context "authorized user" do + it "should return a commit by sha" do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) + response.status.should == 200 + json_response['id'].should == project.repository.commit.id + json_response['title'].should == project.repository.commit.title + end + + it "should return a 404 error if not found" do + get api("/projects/#{project.id}/repository/commits/invalid_sha", user) + response.status.should == 404 + end + end + + context "unauthorized user" do + it "should not return the selected commit" do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") + response.status.should == 401 + end + end + end + + describe "GET /projects:id/repository/commits/:sha/diff" do + context "authorized user" do + before { project.team << [user2, :reporter] } + + it "should return the diff of the selected commit" do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) + response.status.should == 200 + + json_response.should be_an Array + json_response.length.should >= 1 + json_response.first.keys.should include "diff" + end + + it "should return a 404 error if invalid commit" do + get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) + response.status.should == 404 + end + end + + context "unauthorized user" do + it "should not return the diff of the selected commit" do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") + response.status.should == 401 + end + end + end +end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index acef7df8777..fa25a4bec6a 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -9,6 +9,36 @@ describe API::API do let!(:project) { create(:project, namespace: user.namespace ) } before { project.team << [user, :developer] } + describe "GET /projects/:id/repository/files" do + it "should return file info" do + params = { + file_path: 'app/models/key.rb', + ref: 'master', + } + + get api("/projects/#{project.id}/repository/files", user), params + response.status.should == 200 + json_response['file_path'].should == 'app/models/key.rb' + json_response['file_name'].should == 'key.rb' + Base64.decode64(json_response['content']).lines.first.should == "class Key < ActiveRecord::Base\n" + end + + it "should return a 400 bad request if no params given" do + get api("/projects/#{project.id}/repository/files", user) + response.status.should == 400 + end + + it "should return a 404 if such file does not exist" do + params = { + file_path: 'app/models/application.rb', + ref: 'master', + } + + get api("/projects/#{project.id}/repository/files", user), params + response.status.should == 404 + end + end + describe "POST /projects/:id/repository/files" do let(:valid_params) { { diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 47008728252..99d966edc38 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -103,77 +103,6 @@ describe API::API do end end - describe "GET /projects/:id/repository/commits" do - context "authorized user" do - before { project.team << [user2, :reporter] } - - it "should return project commits" do - get api("/projects/#{project.id}/repository/commits", user) - response.status.should == 200 - - json_response.should be_an Array - json_response.first['id'].should == project.repository.commit.id - end - end - - context "unauthorized user" do - it "should not return project commits" do - get api("/projects/#{project.id}/repository/commits") - response.status.should == 401 - end - end - end - - describe "GET /projects:id/repository/commits/:sha" do - context "authorized user" do - it "should return a commit by sha" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - response.status.should == 200 - json_response['id'].should == project.repository.commit.id - json_response['title'].should == project.repository.commit.title - end - - it "should return a 404 error if not found" do - get api("/projects/#{project.id}/repository/commits/invalid_sha", user) - response.status.should == 404 - end - end - - context "unauthorized user" do - it "should not return the selected commit" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - response.status.should == 401 - end - end - end - - describe "GET /projects:id/repository/commits/:sha/diff" do - context "authorized user" do - before { project.team << [user2, :reporter] } - - it "should return the diff of the selected commit" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - response.status.should == 200 - - json_response.should be_an Array - json_response.length.should >= 1 - json_response.first.keys.should include "diff" - end - - it "should return a 404 error if invalid commit" do - get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - response.status.should == 404 - end - end - - context "unauthorized user" do - it "should not return the diff of the selected commit" do - get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - response.status.should == 401 - end - end - end - describe "GET /projects/:id/repository/tree" do context "authorized user" do before { project.team << [user2, :reporter] } |