From 77874bd7c855d574ea3965098c5214dab6b45f05 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 14:26:06 +0100 Subject: [PATCH 01/18] dradis-api - getting ready for v3 --- engines/dradis-api/CHANGELOG | 2 + engines/dradis-api/README.md | 19 ++ .../ce/api/v3/attachments_controller.rb | 85 +++++ .../dradis/ce/api/v3/evidence_controller.rb | 57 ++++ .../dradis/ce/api/v3/issues_controller.rb | 73 ++++ .../dradis/ce/api/v3/nodes_controller.rb | 51 +++ .../dradis/ce/api/v3/notes_controller.rb | 58 ++++ .../v3/attachments/_attachment.json.jbuilder | 2 + .../api/v3/attachments/create.json.jbuilder | 1 + .../ce/api/v3/attachments/index.json.jbuilder | 1 + .../ce/api/v3/attachments/show.json.jbuilder | 1 + .../api/v3/attachments/update.json.jbuilder | 1 + .../api/v3/evidence/_evidence.json.jbuilder | 6 + .../ce/api/v3/evidence/create.json.jbuilder | 1 + .../ce/api/v3/evidence/index.json.jbuilder | 1 + .../ce/api/v3/evidence/show.json.jbuilder | 1 + .../ce/api/v3/evidence/update.json.jbuilder | 1 + .../ce/api/v3/issues/_issue.json.jbuilder | 2 + .../ce/api/v3/issues/create.json.jbuilder | 1 + .../ce/api/v3/issues/index.json.jbuilder | 1 + .../ce/api/v3/issues/show.json.jbuilder | 1 + .../ce/api/v3/issues/update.json.jbuilder | 1 + .../ce/api/v3/nodes/_node.json.jbuilder | 9 + .../ce/api/v3/nodes/create.json.jbuilder | 1 + .../ce/api/v3/nodes/index.json.jbuilder | 1 + .../dradis/ce/api/v3/nodes/show.json.jbuilder | 1 + .../ce/api/v3/nodes/update.json.jbuilder | 1 + .../ce/api/v3/notes/_note.json.jbuilder | 1 + .../ce/api/v3/notes/create.json.jbuilder | 1 + .../ce/api/v3/notes/index.json.jbuilder | 1 + .../dradis/ce/api/v3/notes/show.json.jbuilder | 1 + .../ce/api/v3/notes/update.json.jbuilder | 1 + engines/dradis-api/config/routes.rb | 19 +- .../dradis/ce/api/v3/attachments_spec.rb | 293 ++++++++++++++++ .../dradis/ce/api/v3/evidence_spec.rb | 307 +++++++++++++++++ .../requests/dradis/ce/api/v3/issues_spec.rb | 220 ++++++++++++ .../requests/dradis/ce/api/v3/nodes_spec.rb | 205 +++++++++++ .../requests/dradis/ce/api/v3/notes_spec.rb | 319 ++++++++++++++++++ 38 files changed, 1747 insertions(+), 1 deletion(-) create mode 100644 engines/dradis-api/CHANGELOG create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/attachments/_attachment.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/attachments/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/attachments/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/attachments/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/attachments/update.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/evidence/_evidence.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/evidence/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/evidence/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/evidence/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/evidence/update.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/issues/_issue.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/issues/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/issues/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/issues/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/issues/update.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/nodes/_node.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/nodes/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/nodes/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/nodes/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/nodes/update.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/notes/_note.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/notes/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/notes/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/notes/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/notes/update.json.jbuilder create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/issues_spec.rb create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/notes_spec.rb diff --git a/engines/dradis-api/CHANGELOG b/engines/dradis-api/CHANGELOG new file mode 100644 index 000000000..88de0c0fa --- /dev/null +++ b/engines/dradis-api/CHANGELOG @@ -0,0 +1,2 @@ +v2 (Aug 2023) + - Add Boards, Lists, Cards endpoints. diff --git a/engines/dradis-api/README.md b/engines/dradis-api/README.md index 9d15c57cf..ae3182e68 100644 --- a/engines/dradis-api/README.md +++ b/engines/dradis-api/README.md @@ -4,5 +4,24 @@ This plugin provides an external HTTP API that you can use to query / publish data to your Dradis instance. +## Bumping the API version + +Rewatch: http://railscasts.com/episodes/350-rest-api-versioning + +- When we bump the API version, we copy everything from the previous version and +start making changes while leaving the originals untouched. +- This means the entire controllers/vX/ and views/vX folders. +- Initially it duplicates the code, but eventually the new version is going to +evolve over time, while the original version will remain a snapshot of the +functionality that's frozen in time. +- I think we can safely deprecate older API versions after 2 years. See the +comments in the v1 controllers/ files for guidance on what to include in the +deprecated files. +- You'll also need to duplicate the routes block, and update the :default route +constraint to point to the new version. +- You'll need to duplicate the request specs too. +- Update the engine's CHANGELOG w/ a list of the breaking changes. + + ## Links, licensing, etc. See the main repo's [README.md](https://github.com/dradis/dradis-ce/blob/master/README.md) diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb new file mode 100644 index 000000000..f5401278e --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb @@ -0,0 +1,85 @@ +module Dradis::CE::API + module V1 + class AttachmentsController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + before_action :set_node + + skip_before_action :json_required, :only => [:create] + + def index + @attachments = @node.attachments.each(&:close) + end + + def show + begin + @attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id } ) + rescue + raise ActiveRecord::RecordNotFound, "Couldn't find attachment with filename '#{params[:filename]}'" + end + end + + def create + uploaded_files = params.fetch(:files, []) + + @attachments = [] + uploaded_files.each do |uploaded_file| + attachment_name = NamingService.name_file( + original_filename: uploaded_file.original_filename, + pathname: Attachment.pwd.join(@node.id.to_s) + ) + + attachment = Attachment.new(attachment_name, node_id: @node.id) + attachment << uploaded_file.read + attachment.save + + @attachments << attachment + end + + if @attachments.any? && @attachments.count == uploaded_files.count + render status: 201 + else + render status: 422 + end + end + + def update + attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id } ) + attachment.close + + begin + new_name = CGI::unescape(attachment_params[:filename]) + destination = Attachment.pwd.join(@node.id.to_s, new_name).to_s + + if !File.exist?(destination) && !destination.match(/^#{Attachment.pwd}/).nil? + File.rename attachment.fullpath, destination + @attachment = Attachment.find(new_name, conditions: { node_id: @node.id } ) + else + raise "Destination file already exists" + end + rescue + @attachment = attachment + render status: 422 + end + end + + def destroy + @attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id} ) + @attachment.delete + + render_successful_destroy_message + end + + private + + def set_node + @node = current_project.nodes.find(params[:node_id]) + end + + def attachment_params + params.require(:attachment).permit(:filename) + end + end + end +end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb new file mode 100644 index 000000000..f321f0e02 --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb @@ -0,0 +1,57 @@ +module Dradis::CE::API + module V1 + class EvidenceController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + before_action :set_node + + def index + @evidence = @node.evidence.order('updated_at desc') + @evidence = @evidence.page(params[:page].to_i) if params[:page] + end + + def show + @evidence = @node.evidence.find(params[:id]) + end + + def create + @evidence = @node.evidence.build(evidence_params) + if @evidence.save + track_created(@evidence) + render status: 201, location: node_evidence_path(@node, @evidence) + else + render_validation_errors(@evidence) + end + end + + def update + @evidence = @node.evidence.find(params[:id]) + if @evidence.update(evidence_params) + track_updated(@evidence) + render evidence: @evidence + else + render_validation_errors(@evidence) + end + end + + def destroy + @evidence = @node.evidence.find(params[:id]) + @evidence.destroy + track_destroyed(@evidence) + render_successful_destroy_message + end + + private + + def set_node + @node = current_project.nodes.find(params[:node_id]) + end + + def evidence_params + params.require(:evidence).permit(:content, :issue_id) + end + + end + end +end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb new file mode 100644 index 000000000..7ace96442 --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb @@ -0,0 +1,73 @@ +module Dradis::CE::API + module V1 + class IssuesController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + before_action :set_issue, except: [:index] + before_action :validate_state, only: [:create, :update] + + def index + @issues = current_project.issues.includes(:tags).order('updated_at desc') + @issues = @issues.page(params[:page].to_i) if params[:page] + @issues = @issues.sort + end + + def show + end + + def create + @issue.assign_attributes(issue_params) + @issue.author = current_user.email + @issue.category = Category.issue + @issue.node = current_project.issue_library + + if @issue.save + track_created(@issue) + @issue.tag_from_field_content! + render status: 201, location: dradis_api.issue_url(@issue) + else + render_validation_errors(@issue) + end + end + + def update + if @issue.update(issue_params) + track_updated(@issue) + render node: @node + else + render_validation_errors(@issue) + end + end + + def destroy + @issue.destroy + track_destroyed(@issue) + render_successful_destroy_message + end + + private + + def issue_params + params.require(:issue).permit(:state, :text) + end + + def set_issue + if params[:id] + @issue = current_project.issues.find(params[:id]) + else + @issue = current_project.issues.new + end + end + + def validate_state + return if issue_params[:state].nil? + + unless Issue.states.keys.include? issue_params[:state] + @issue.errors.add(:state, 'invalid value.') + render_validation_errors(@issue) + end + end + end + end +end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb new file mode 100644 index 000000000..1914835d6 --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb @@ -0,0 +1,51 @@ +module Dradis::CE::API + module V1 + class NodesController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + def index + @nodes = current_project.nodes.user_nodes.includes(:evidence, :notes, evidence: [:issue]).order('updated_at desc') + @nodes = @nodes.page(params[:page].to_i) if params[:page] + end + + def show + @node = current_project.nodes.includes(:evidence, :notes, evidence: [:issue]).find(params[:id]) + end + + def create + @node = current_project.nodes.new(node_params) + + if @node.save + track_created(@node) + render status: 201, location: dradis_api.node_url(@node) + else + render_validation_errors(@node) + end + end + + def update + @node = current_project.nodes.find(params[:id]) + if @node.update(node_params) + track_updated(@node) + render node: @node + else + render_validation_errors(@node) + end + end + + def destroy + node = current_project.nodes.find(params[:id]) + node.destroy + track_destroyed(node) + render_successful_destroy_message + end + + protected + + def node_params + params.require(:node).permit(:label, :type_id, :parent_id, :position) + end + end + end +end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb new file mode 100644 index 000000000..10cf48a8a --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb @@ -0,0 +1,58 @@ +module Dradis::CE::API + module V1 + class NotesController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + before_action :set_node + + def index + @notes = @node.notes.order('updated_at desc') + @notes = @notes.page(params[:page].to_i) if params[:page] + end + + def show + @note = @node.notes.find(params[:id]) + end + + def create + @note = @node.notes.build(note_params) + @note.category ||= Category.default + if @note.save + track_created(@note) + render status: 201, location: node_note_path(@node, @note) + else + render_validation_errors(@note) + end + end + + def update + @note = @node.notes.find(params[:id]) + if @note.update(note_params) + track_updated(@note) + render note: @note + else + render_validation_errors(@note) + end + end + + def destroy + @note = @node.notes.find(params[:id]) + @note.destroy + track_destroyed(@note) + render_successful_destroy_message + end + + private + + def set_node + @node = current_project.nodes.find(params[:node_id]) + end + + def note_params + params.require(:note).permit(:category_id, :text) + end + + end + end +end diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/_attachment.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/_attachment.json.jbuilder new file mode 100644 index 000000000..7e8146c6a --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/_attachment.json.jbuilder @@ -0,0 +1,2 @@ +json.filename attachment.filename +json.link main_app.project_node_attachment_path(current_project, @node, attachment.filename) diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/create.json.jbuilder new file mode 100644 index 000000000..bfea03fba --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/create.json.jbuilder @@ -0,0 +1 @@ +json.array! @attachments, partial: 'attachment', as: :attachment diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/index.json.jbuilder new file mode 100644 index 000000000..bfea03fba --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @attachments, partial: 'attachment', as: :attachment diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/show.json.jbuilder new file mode 100644 index 000000000..59c969898 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'attachment', attachment: @attachment diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/update.json.jbuilder new file mode 100644 index 000000000..59c969898 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/attachments/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'attachment', attachment: @attachment diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/_evidence.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/_evidence.json.jbuilder new file mode 100644 index 000000000..6d1c38732 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/_evidence.json.jbuilder @@ -0,0 +1,6 @@ +json.(evidence, :id, :author, :content, :fields) +json.issue do |json| + json.id evidence.issue_id + json.title evidence.issue.title + json.url dradis_api.issue_url(evidence.issue_id) +end diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/create.json.jbuilder new file mode 100644 index 000000000..5d185d168 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'evidence', evidence: @evidence diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/index.json.jbuilder new file mode 100644 index 000000000..5c269b4db --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @evidence, partial: 'evidence', as: :evidence diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/show.json.jbuilder new file mode 100644 index 000000000..5d185d168 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'evidence', evidence: @evidence diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/update.json.jbuilder new file mode 100644 index 000000000..5d185d168 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/evidence/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'evidence', evidence: @evidence diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/issues/_issue.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/_issue.json.jbuilder new file mode 100644 index 000000000..7919cfb81 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/_issue.json.jbuilder @@ -0,0 +1,2 @@ +json.(issue, :id, :author, :title, :fields, :state, :text, :created_at, :updated_at) +json.tags issue.tags, :color, :display_name diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/issues/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/create.json.jbuilder new file mode 100644 index 000000000..9b46d5f82 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'issue', issue: @issue diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/issues/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/index.json.jbuilder new file mode 100644 index 000000000..9228c78db --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @issues, partial: 'issue', as: :issue diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/issues/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/show.json.jbuilder new file mode 100644 index 000000000..9b46d5f82 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'issue', issue: @issue diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/issues/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/update.json.jbuilder new file mode 100644 index 000000000..9b46d5f82 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/issues/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'issue', issue: @issue diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/_node.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/_node.json.jbuilder new file mode 100644 index 000000000..dbd11c0b2 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/_node.json.jbuilder @@ -0,0 +1,9 @@ +json.(node, :id, :label, :type_id, :parent_id, :position, :created_at, :updated_at) + +json.evidence node.evidence do |evidence| + json.partial! evidence +end + +json.notes node.notes do |note| + json.partial! note +end diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/create.json.jbuilder new file mode 100644 index 000000000..b1745574e --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'node', node: @node diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/index.json.jbuilder new file mode 100644 index 000000000..fb4e3ff66 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @nodes, partial: 'node', as: :node diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/show.json.jbuilder new file mode 100644 index 000000000..b1745574e --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'node', node: @node diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/update.json.jbuilder new file mode 100644 index 000000000..b1745574e --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/nodes/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'node', node: @node diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/notes/_note.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/_note.json.jbuilder new file mode 100644 index 000000000..c28ebdc46 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/_note.json.jbuilder @@ -0,0 +1 @@ +json.(note, :id, :author, :category_id, :title, :fields, :text) diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/notes/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/create.json.jbuilder new file mode 100644 index 000000000..13a17106a --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'note', note: @note diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/notes/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/index.json.jbuilder new file mode 100644 index 000000000..346e0a5fc --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @notes, partial: 'note', as: :note diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/notes/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/show.json.jbuilder new file mode 100644 index 000000000..13a17106a --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'note', note: @note diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/notes/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/update.json.jbuilder new file mode 100644 index 000000000..13a17106a --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/notes/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'note', note: @note diff --git a/engines/dradis-api/config/routes.rb b/engines/dradis-api/config/routes.rb index 017844318..f635edbe0 100644 --- a/engines/dradis-api/config/routes.rb +++ b/engines/dradis-api/config/routes.rb @@ -1,7 +1,7 @@ Dradis::CE::API::Engine::routes.draw do scope path: :api do defaults format: 'json' do - scope module: :v1, constraints: Dradis::CE::API::RoutingConstraints.new(version: 1, default: true) do + scope module: :v1, constraints: Dradis::CE::API::RoutingConstraints.new(version: 1) do resources :issues resources :nodes do resources :evidence @@ -11,6 +11,23 @@ end end end + + scope module: :v3, constraints: Dradis::CE::API::RoutingConstraints.new(version: 3, default: true) do + resources :boards do + resources :lists do + resources :cards do + end + end + end + resources :issues + resources :nodes do + resources :evidence + resources :notes + constraints(filename: /.*/) do + resources :attachments, param: :filename + end + end + end end end end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb new file mode 100644 index 000000000..726135288 --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb @@ -0,0 +1,293 @@ +require 'rails_helper' + +describe "Attachments API" do + include_context "project scoped API" + include_context "https" + + let(:node) { create(:node, project: current_project) } + + context "as unauthenticated user" do + [ + ['get', '/api/nodes/1/attachments/'], + ['get', '/api/nodes/1/attachments/image.jpg'], + ['post', '/api/nodes/1/attachments/'], + ['put', '/api/nodes/1/attachments/image.jpg'], + ['patch', '/api/nodes/1/attachments/image.jpg'], + ['delete', '/api/nodes/1/attachments/image.jpg'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context "as authorized user" do + include_context "authorized API user" + + before(:each) do + FileUtils.rm_rf Dir[Attachment.pwd.join('*')] until Dir[Attachment.pwd.join('*')].count == 0 + end + + after(:all) do + FileUtils.rm_rf Dir[Attachment.pwd.join('*')] + end + + describe "GET /api/nodes/:node_id/attachments" do + before do + @attachments = ["image0.png", "image1.png", "image2.png"] + @attachments.each do |attachment| + create(:attachment, filename: attachment, node: node) + end + + # an attachment in another node + create(:attachment, filename: "image3.png", node: create(:node, project: current_project)) + + get "/api/nodes/#{node.id}/attachments", env: @env + end + + let(:retrieved_attachments) { JSON.parse(response.body) } + + it "responds with HTTP code 200" do + expect(response.status).to eq(200) + end + + it "retrieves all the attachments for the given node" do + expect(retrieved_attachments.count).to eq @attachments.count + retrieved_filenames = retrieved_attachments.map{ |json| json['filename'] } + expect(retrieved_filenames).to match_array(@attachments) + end + + it "returns JSON information about attachments" do + attachment_0 = retrieved_attachments.detect { |n| n["filename"] == "image0.png" } + attachment_1 = retrieved_attachments.detect { |n| n["filename"] == "image1.png" } + attachment_2 = retrieved_attachments.detect { |n| n["filename"] == "image2.png" } + + expect(attachment_0).to eq({ + "filename" => "image0.png", + "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image0.png" + }) + expect(attachment_1).to eq({ + "filename" => "image1.png", + "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image1.png" + }) + expect(attachment_2).to eq({ + "filename" => "image2.png", + "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image2.png" + }) + end + + it "doesn't return attachments from other nodes" do + expect(retrieved_attachments.map{ |json| json['filename'] }).not_to include "image3.png" + end + end + + describe "GET /api/nodes/:node_id/attachments/:filename" do + before do + create(:attachment, filename: "image.png", node: node) + + get "/api/nodes/#{node.id}/attachments/image.png", env: @env + end + + it "responds with HTTP code 200" do + expect(response.status).to eq 200 + end + + it "responds with HTTP code 404 when not found" do + get "/api/nodes/#{node.id}/attachments/image_ko.png", env: @env + expect(response.status).to eq 404 + json_response = JSON.parse(response.body) + expect(json_response["message"]).to eq "Couldn't find attachment with filename 'image_ko.png'" + end + + it "returns JSON information about the attachment" do + retrieved_attachment = JSON.parse(response.body) + expect(retrieved_attachment.keys).to match_array(%w[filename link]) + expect(retrieved_attachment["filename"]).to eq "image.png" + expect(retrieved_attachment["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image.png" + end + end + + describe "POST /api/nodes/:node_id/attachments" do + let(:post_attachment) { + file = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) + params = { files: [file] } + url = "/api/nodes/#{node.id}/attachments" + + post url , params: params, env: @env + } + + it "returns 201 when file saved" do + post_attachment + expect(response.status).to eq 201 + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'rails.png'))).to be true + end + + it "returns 422 when no file saved" do + url = "/api/nodes/#{node.id}/attachments" + post url , params: {}, env: @env + + expect(response.status).to eq 422 + expect(File.exist?(Attachment.pwd.join(node.id.to_s))).to be false + end + + it "auto-renames the upload if an attachment with the same name already exists" do + node_attachments = Attachment.pwd.join(node.id.to_s) + FileUtils.mkdir_p( node_attachments ) + + create(:attachment, filename: "rails.png", node: node) + expect(Dir["#{node_attachments}/*"].count).to eq(1) + + post_attachment + + expect(Dir["#{node_attachments}/*"].count).to eq(2) + end + + it "returns JSON information about the attachments" do + file1 = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) + file2 = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) + params = { files: [file1, file2] } + url = "/api/nodes/#{node.id}/attachments" + + post url , params: params, env: @env + + retrieved_attachments = JSON.parse(response.body) + + attachment_0 = retrieved_attachments.detect { |n| n["filename"] == "rails.png" } + attachment_1 = retrieved_attachments.detect { |n| n["filename"] == "rails_copy-01.png" } + + expect(attachment_0.keys).to match_array %w[filename link] + expect(attachment_0["filename"]).to eq "rails.png" + expect(attachment_0["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails.png" + expect(attachment_1.keys).to match_array %w[filename link] + expect(attachment_1["filename"]).to eq "rails_copy-01.png" + expect(attachment_1["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails_copy-01.png" + end + end + + describe "PUT /api/nodes/:node_id/attachments/:filename" do + before do + create(:attachment, filename: "image.png", node: node) + end + + let(:url) { "/api/nodes/#{node.id}/attachments/image.png" } + let(:put_attachment) { put url, params: params.to_json, env: @env } + + context "when content_type header = application/json" do + include_context "content_type: application/json" + + context "with params for a valid attachment" do + let(:params) { { attachment: { filename: "image_renamed.png" } } } + + it "responds with HTTP code 200 if attachment exists" do + put_attachment + expect(response.status).to eq 200 + end + + it "responds with HTTP code 404 if attachemnt doesn't exist" do + bad_url = "/api/nodes/#{node.id}/attachments/image_ko.png" + put bad_url, params: params, env: @env + expect(response.status).to eq(400) + end + + it "updates the attachment" do + put_attachment + + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be false + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image_renamed.png'))).to be true + end + + it "returns the attributes of the updated attachment as JSON" do + put_attachment + retrieved_attachment = JSON.parse(response.body) + expect(retrieved_attachment["filename"]).to eq "image_renamed.png" + end + + it "responds with HTTP code 422 if attachment already exists" do + create(:attachment, filename: "image_renamed.png", node: node) + put_attachment + expect(response.status).to eq(422) + retrieved_attachment = JSON.parse(response.body) + expect(retrieved_attachment["filename"]).to eq "image.png" + end + end + + context "with params for an invalid attachment" do + let(:params) { { attachment: { filename: "a"*65536 } } } # too long + + it "responds with HTTP code 422" do + put_attachment + expect(response.status).to eq 422 + end + + it "doesn't update the attachment" do + put_attachment + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image_renamed.png'))).to be false + end + end + + context "when no :attachment param is sent" do + let(:params) { {} } + + it "doesn't update the attachment" do + put_attachment + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true + end + + it "responds with HTTP code 422" do + put_attachment + expect(response.status).to eq 422 + end + end + + context "when invalid JSON is sent" do + it "responds with HTTP code 400" do + json_payload = '{"attachment":{"filename":"A malformed label", , }}' + put url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context "when JSON is not sent" do + let(:params) { { attachment: { filename: "image_renamed.jpg" } } } + + it "responds with HTTP code 415" do + put url, params: params, env: @env + expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true + expect(response.status).to eq 415 + end + end + end + + describe "DELETE /api/nodes/:node_id/attachments/:filename" do + let(:attachment) { "image.png" } + + let(:delete_attachment) do + create(:attachment, filename: attachment, node: node) + delete "/api/nodes/#{node.id}/attachments/#{attachment}", env: @env + end + + it "deletes the attachment" do + delete_attachment + expect(File.exist?(Attachment.pwd.join(node.id.to_s, attachment))).to\ + be false + end + + it "responds with error code 200" do + delete_attachment + expect(response.status).to eq(200) + end + + it "returns JSON with a success message" do + delete_attachment + parsed_response = JSON.parse(response.body) + expect(parsed_response["message"]).to eq\ + "Resource deleted successfully" + end + end + end +end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb new file mode 100644 index 000000000..712fe81f3 --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb @@ -0,0 +1,307 @@ +require 'rails_helper' + +describe 'Evidence API' do + + include_context 'project scoped API' + include_context 'https' + + let(:node) { create(:node, project: current_project) } + let(:issue) { create(:issue, node: current_project.issue_library) } + + context 'as unauthenticated user' do + [ + ['get', '/api/nodes/1/evidence/'], + ['get', '/api/nodes/1/evidence/1'], + ['post', '/api/nodes/1/evidence/'], + ['put', '/api/nodes/1/evidence/1'], + ['patch', '/api/nodes/1/evidence/1'], + ['delete', '/api/nodes/1/evidence/1'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + let(:category) { create(:category) } + + describe 'GET /api/nodes/:node_id/evidence' do + before do + @issues = create_list(:issue, 4, node: current_project.issue_library) + @evidence = [ + Evidence.create!(node: node, content: "#[a]#\nA", issue: @issues[0]), + Evidence.create!(node: node, content: "#[b]#\nB", issue: @issues[1]), + Evidence.create!(node: node, content: "#[c]#\nC", issue: @issues[2]), + ] << create_list(:evidence, 30, issue: @issues[3], node: node) + @other_evidence = create(:evidence, issue: issue, node: current_project.issue_library) + get "/api/nodes/#{node.id}/evidence?#{params}", env: @env + end + + let(:retrieved_evidence) { JSON.parse(response.body) } + + context 'without params' do + let(:params) { '' } + + it 'responds with HTTP code 200' do + expect(response.status).to eq(200) + end + + it 'retrieves all the evidence for the given node' do + expect(retrieved_evidence.count).to eq 33 + issue_titles = retrieved_evidence.map { |json| json['issue']['title'] }.uniq + expect(issue_titles).to match_array @issues.map(&:title) + end + + it 'returns JSON data about the evidence\'s fields and issue' do + ev_0 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[0].id } + ev_1 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[1].id } + ev_2 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[2].id } + + expect(ev_0['fields'].keys).to \ + match_array (@evidence[0].local_fields.keys << 'a') + expect(ev_0['fields']['a']).to eq 'A' + expect(ev_0['issue']['title']).to eq @issues[0].title + expect(ev_1['fields'].keys).to \ + match_array (@evidence[2].local_fields.keys << 'b') + expect(ev_1['fields']['b']).to eq 'B' + expect(ev_1['issue']['title']).to eq @issues[1].title + expect(ev_2['fields'].keys).to \ + match_array (@evidence[2].local_fields.keys << 'c') + expect(ev_2['fields']['c']).to eq 'C' + expect(ev_2['issue']['title']).to eq @issues[2].title + end + + it 'doesn\'t return evidence from other nodes' do + retrieved_ids = retrieved_evidence.map { |n| n['id'] } + expect(retrieved_ids).not_to include @other_evidence.id + end + end + + context 'with params' do + let(:params) { 'page=2' } + + it 'returns the paginated evidence' do + expect(retrieved_evidence.count).to eq 8 + + end + end + end + + describe 'GET /api/nodes/:node_id/evidence/:id' do + before do + @issue = create(:issue, node: current_project.issue_library) + @evidence = node.evidence.create!( + content: "#[foo]#\nbar\n#[fizz]#\nbuzz", + issue: @issue, + ) + get "/api/nodes/#{node.id}/evidence/#{@evidence.id}", env: @env + end + + it 'responds with HTTP code 200' do + expect(response.status).to eq 200 + end + + it 'returns JSON information about the evidence' do + retrieved_evidence = JSON.parse(response.body) + expect(retrieved_evidence['id']).to eq @evidence.id + expect(retrieved_evidence['author']).to eq @evidence.author + expect(retrieved_evidence['fields'].keys).to match_array( + @evidence.local_fields.keys + %w(fizz foo) + ) + expect(retrieved_evidence['fields']['foo']).to eq 'bar' + expect(retrieved_evidence['fields']['fizz']).to eq 'buzz' + expect(retrieved_evidence['issue']['id']).to eq @issue.id + expect(retrieved_evidence['issue']['title']).to eq @issue.title + end + end + + describe 'POST /api/nodes/:node_id/evidence' do + let(:url) { "/api/nodes/#{node.id}/evidence" } + let(:issue) { create(:issue, node: current_project.issue_library) } + let(:post_evidence) { post url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid evidence' do + let(:params) { { evidence: { content: 'New evidence', issue_id: issue.id } } } + + it 'responds with HTTP code 201' do + post_evidence + expect(response.status).to eq 201 + end + + it 'creates an evidence' do + expect { post_evidence }.to change { node.evidence.count } + new_evidence = node.evidence.last + expect(new_evidence.content).to eq 'New evidence' + expect(new_evidence.issue).to eq issue + end + + let(:submit_form) { post_evidence } + include_examples 'creates an Activity', :create, Evidence + include_examples 'sets the whodunnit', :create, Evidence + end + + context 'with params for an invalid evidence' do + let(:params) { { evidence: { content: 'New evidence' } } } # no issue + + it 'responds with HTTP code 422' do + post_evidence + expect(response.status).to eq 422 + end + + it "doesn't create an evidence" do + expect { post_evidence }.not_to change { Evidence.count } + end + end + + context 'when no :evidence param is sent' do + let(:params) { {} } + + it "doesn't create an evidence" do + expect { post_evidence }.not_to change { Evidence.count } + end + + it 'responds with HTTP code 422' do + post_evidence + expect(response.status).to eq(422) + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"evidence":{"label":"A malformed label", , }}' + post url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + it 'responds with HTTP code 415' do + params = { evidence: {} } + post url, params: params, env: @env + expect(response.status).to eq(415) + end + end + end + + describe 'PUT /api/nodes/:node_id/evidence/:id' do + let(:evidence) do + create(:evidence, node: node, content: 'My content', issue: issue) + end + + let(:url) { "/api/nodes/#{node.id}/evidence/#{evidence.id}" } + let(:put_evidence) { put url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid evidence' do + let(:params) { { evidence: { content: 'New content' } } } + + it 'responds with HTTP code 200' do + put_evidence + expect(response.status).to eq 200 + end + + it 'updates the evidence' do + put_evidence + expect(evidence.reload.content).to eq 'New content' + end + + it 'returns the attributes of the updated evidence as JSON' do + put_evidence + retrieved_evidence = JSON.parse(response.body) + expect(retrieved_evidence['content']).to eq 'New content' + end + + let(:submit_form) { put_evidence } + let(:model) { evidence } + include_examples 'creates an Activity', :update + include_examples 'sets the whodunnit', :update + end + + context 'with params for an invalid evidence' do + let(:params) { { evidence: { content: 'a' * 65536 } } } # too long + + it 'responds with HTTP code 422' do + put_evidence + expect(response.status).to eq 422 + end + + it "doesn't update the evidence" do + expect { put_evidence }.not_to change { evidence.reload.attributes } + end + end + + context 'when no :evidence param is sent' do + let(:params) { {} } + + it "doesn't update the evidence" do + expect { put_evidence }.not_to change { evidence.reload.attributes } + end + + it 'responds with HTTP code 422' do + put_evidence + expect(response.status).to eq 422 + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"evidence":{"label":"A malformed label", , }}' + put url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + let(:params) { { evidence: { content: 'New Evidence' } } } + + it 'responds with HTTP code 415' do + expect { put url, params: params, env: @env }.not_to change { evidence.reload.attributes } + expect(response.status).to eq 415 + end + end + end + + describe 'DELETE /api/nodes/:node_id/evidence/:id' do + let(:evidence) { create(:evidence, node: node, content: 'My Evidence', issue: issue) } + + let(:delete_evidence) do + delete "/api/nodes/#{node.id}/evidence/#{evidence.id}", env: @env + end + + it 'deletes the evidence' do + evidence_id = evidence.id + delete_evidence + expect(Evidence.find_by_id(evidence_id)).to be_nil + end + + it 'responds with error code 200' do + delete_evidence + expect(response.status).to eq(200) + end + + it 'returns JSON with a success message' do + delete_evidence + parsed_response = JSON.parse(response.body) + expect(parsed_response['message']).to eq\ + 'Resource deleted successfully' + end + + let(:submit_form) { delete_evidence } + let(:model) { evidence } + include_examples 'creates an Activity', :destroy + end + end +end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/issues_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/issues_spec.rb new file mode 100644 index 000000000..51744626a --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/issues_spec.rb @@ -0,0 +1,220 @@ +require 'rails_helper' + +describe 'Issues API' do + + include_context 'project scoped API' + include_context 'https' + + context 'as unauthenticated user' do + [ + ['get', '/api/issues/'], + ['get', '/api/issues/1'], + ['post', '/api/issues/'], + ['put', '/api/issues/1'], + ['patch', '/api/issues/1'], + ['delete', '/api/issues/1'], + ].each do |verb, url| + describe '#{verb.upcase} #{url}' do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + describe 'GET /api/issues' do + before(:each) do + @issues = create_list(:issue, 30, node: current_project.issue_library).sort_by(&:title) + + get path, env: @env + expect(response.status).to eq(200) + + @retrieved_issues = JSON.parse(response.body) + end + + context 'without params' do + let(:path) { '/api/issues' } + + it 'retrieves all the issues' do + titles = @issues.map(&:title) + retrieved_titles = @retrieved_issues.map { |json| json['title'] } + + expect(@retrieved_issues.count).to eq(@issues.count) + expect(retrieved_titles).to match_array(titles) + end + + it 'includes fields' do + @retrieved_issues.each do |issue| + expect(issue).to have_key('id') + db_issue = Issue.find(issue['id']) + + expect(issue['fields']).not_to be_empty + expect(issue['fields'].count).to eq(db_issue.fields.count) + expect(issue['fields'].keys).to eq(db_issue.fields.keys) + end + end + end + + context 'with params' do + let(:path) { '/api/issues?page=2' } + + it 'retrieves the paginated issues' do + expect(@retrieved_issues.count).to eq(5) + end + end + end + + describe 'GET /api/issue/:id' do + before(:each) do + @issue = create(:issue, :tagged_issue, node: current_project.issue_library, text: "#[a]#\nb\n\n#[c]#\nd\n\n#[e]#\nf\n\n") + + get "/api/issues/#{ @issue.id }", env: @env + expect(response.status).to eq(200) + + @retrieved_issue = JSON.parse(response.body) + end + + it 'retrieves a specific issue' do + expect(@retrieved_issue['id']).to eq @issue.id + end + + it 'includes fields' do + expect(@retrieved_issue['fields']).not_to be_empty + expect(@retrieved_issue['fields'].keys).to eq @issue.fields.keys + expect(@retrieved_issue['fields'].count).to eq @issue.fields.count + end + + it 'includes tags' do + tag = @issue.tags.first + expect(@retrieved_issue['tags']).to eq [{ 'color' => tag.color, 'display_name' => tag.display_name }] + end + + it 'includes the author' do + expect(@retrieved_issue['author']).to eq @issue.author + end + + it 'includes the state' do + expect(@retrieved_issue['state']).to eq @issue.state + end + end + + describe 'POST /api/issues' do + let(:valid_params) do + { issue: { text: "#[Title]#\nRspec issue\n\n#[c]#\nd\n\n#[e]#\nf\n\n", state: 'ready_for_review' } } + end + let(:valid_post) do + post '/api/issues', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + end + + it 'creates a new issue' do + expect { valid_post }.to change { current_project.issues.count }.by(1) + expect(response.status).to eq(201) + retrieved_issue = JSON.parse(response.body) + expect(retrieved_issue['text']).to eq valid_params[:issue][:text] + end + + it 'tags the issue from the Tags field' do + tag_name = '!2ca02c_info' + valid_params[:issue][:text] << "#[Tags]#\n\n#{tag_name}\n\n" + + expect { valid_post }.to change { current_project.issues.count }.by(1) + expect(response.status).to eq(201) + + retrieved_issue = JSON.parse(response.body) + database_issue = current_project.issues.find(retrieved_issue['id']) + + expect(database_issue.tag_list).to eq(tag_name) + end + + it 'sets the issue state' do + expect { valid_post }.to change { current_project.issues.count }.by(1) + expect(response.status).to eq(201) + + retrieved_issue = JSON.parse(response.body) + expect(retrieved_issue['state']).to eq('ready_for_review') + end + + let(:submit_form) { valid_post } + include_examples 'creates an Activity', :create, Issue + include_examples 'sets the whodunnit', :create, Issue + + it 'throws 415 unless JSON is sent' do + params = { issue: { name: 'Bad Issue' } } + post '/api/issues', params: params, env: @env + expect(response.status).to eq(415) + end + + it 'throws 422 if issue is invalid' do + params = { issue: { text: 'A' * (65535 + 1) } } + expect { + post '/api/issues', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + }.not_to change { current_project.issues.count } + expect(response.status).to eq(422) + end + + it 'throws 422 if state is invalid' do + params = { issue: { text: "#[Title]#\nIssue test\n", state: 'fakestate' } } + expect { + post '/api/issues', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + }.not_to change { current_project.issues.count } + expect(response.status).to eq(422) + end + end + + describe 'PUT /api/issues/:id' do + let(:issue) { create(:issue, node: current_project.issue_library, text: 'Existing Issue') } + let(:valid_params) { { issue: { text: 'Updated Issue', state: 'ready_for_review' } } } + let(:valid_put) do + put "/api/issues/#{issue.id}", params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + end + + it 'updates a issue' do + valid_put + expect(response.status).to eq(200) + + expect(current_project.issues.find(issue.id).text).to eq valid_params[:issue][:text] + + retrieved_issue = JSON.parse(response.body) + expect(retrieved_issue['text']).to eq valid_params[:issue][:text] + expect(retrieved_issue['state']).to eq valid_params[:issue][:state] + end + + let(:submit_form) { valid_put } + let(:model) { issue } + include_examples 'creates an Activity', :update + include_examples 'sets the whodunnit', :update + + it 'throws 415 unless JSON is sent' do + params = { issue: { text: 'Bad issuet' } } + put "/api/issues/#{ issue.id }", params: params, env: @env + expect(response.status).to eq(415) + end + + it 'throws 422 if issue is invalid' do + params = { issue: { text: 'B' * (65535 + 1) } } + put "/api/issues/#{ issue.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + end + + describe 'DELETE /api/issue/:id' do + let(:issue) { create(:issue, node: current_project.issue_library) } + + let(:delete_issue) { delete "/api/issues/#{ issue.id }", env: @env } + + it 'deletes a issue' do + delete_issue + expect(response.status).to eq(200) + expect { current_project.issues.find(issue.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + let(:submit_form) { delete_issue } + let(:model) { issue } + include_examples 'creates an Activity', :destroy + end + end +end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb new file mode 100644 index 000000000..bc69fc7ef --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb @@ -0,0 +1,205 @@ +require 'rails_helper' + +describe 'Nodes API' do + + include_context 'project scoped API' + include_context 'https' + + context 'as unauthenticated user' do + [ + ['get', '/api/nodes/'], + ['get', '/api/nodes/1'], + ['post', '/api/nodes/'], + ['put', '/api/nodes/1'], + ['patch', '/api/nodes/1'], + ['delete', '/api/nodes/1'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + describe 'GET /api/nodes' do + before do + @nodes = create_list(:node, 30, project: current_project).sort_by(&:updated_at) + @node_labels = @nodes.map(&:label) + + get "/api/nodes?#{params}", env: @env + + expect(response.status).to eq(200) + @retrieved_nodes = JSON.parse(response.body) + end + + context 'without params' do + let(:params) { '' } + + it 'retrieves all the nodes' do + retrieved_node_labels = @retrieved_nodes.map{ |p| p['label'] } + + expect(@retrieved_nodes.count).to eq(@nodes.count) + expect(retrieved_node_labels).to match_array(@node_labels) + end + end + + context 'with params' do + let(:params) { 'page=2' } + + it 'retrieves the paginated nodes' do + expect(@retrieved_nodes.count).to eq(5) + end + end + end + + describe 'GET /api/nodes/:id' do + it 'retrieves a specific node' do + node = create(:node, label: 'Existing Node', project: current_project) + + get "/api/nodes/#{ node.id }", env: @env + expect(response.status).to eq(200) + + retrieved_node = JSON.parse(response.body) + expect(retrieved_node['label']).to eq node.label + end + end + + describe 'POST /api/nodes' do + let!(:parent_node_id) { Project.new.plugin_parent_node.id } + let(:valid_post) do + post '/api/nodes', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + end + let(:valid_params) do + { + node: { + label: 'New Node', + type_id: Node::Types::HOST, + parent_id: parent_node_id, + position: 3 + } + } + end + + it 'creates a new node' do + expect{valid_post}.to change{Node.count}.by(1) + expect(response.status).to eq(201) + + retrieved_node = JSON.parse(response.body) + + expect(response.location).to eq(dradis_api.node_url(retrieved_node['id'])) + + valid_params[:node].each do |attr, value| + expect(retrieved_node[attr.to_s]).to eq value + end + end + + # Activity shared example was originally written for feature requests and + # expects a 'submit_form' let variable to be defined: + let(:submit_form) { valid_post } + include_examples 'creates an Activity', :create, Node + + it 'throws 415 unless JSON is sent' do + params = { node: { } } + post '/api/nodes', params: params, env: @env + expect(response.status).to eq(415) + end + + it 'throws 422 if node is invalid' do + params = { node: { label: '' } } + post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 422 if no :node param is sent' do + params = { } + post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 400 if invalid JSON is sent' do + invalid_tokens = ', , ' + json_payload = %Q|{"node":{"label":"A malformed label"#{ invalid_tokens }}}| + post '/api/nodes', params: json_payload, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(400) + end + end + + describe 'PUT /api/nodes/:id' do + + let(:node) { create(:node, label: 'Existing Node', project: current_project) } + + let(:valid_put) do + put "/api/nodes/#{ node.id }", params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + end + let(:valid_params) { { node: { label: 'Updated Node' } } } + + it 'updates a node' do + valid_put + expect(response.status).to eq(200) + expect(current_project.nodes.find(node.id).label).to eq valid_params[:node][:label] + retrieved_node = JSON.parse(response.body) + expect(retrieved_node['label']).to eq valid_params[:node][:label] + end + + let(:submit_form) { valid_put } + let(:model) { node } + include_examples 'creates an Activity', :update + + it 'assigns :type_id' do + params = { node: { type_id: Node::Types::HOST } } + put "/api/nodes/#{ node.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(200) + expect(current_project.nodes.find(node.id).type_id).to eq(Node::Types::HOST) + end + + it 'throws 415 unless JSON is sent' do + params = { node: { label: 'Bad Node' } } + put "/api/nodes/#{ node.id }", params: params, env: @env + expect(response.status).to eq(415) + end + + it 'throws 422 if node is invalid' do + params = { node: { label: '' } } + put "/api/nodes/#{ node.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 422 if no :node param is sent' do + params = { } + put "/api/nodes/#{ node.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 400 if invalid JSON is sent' do + invalid_tokens = ', , ' + json_payload = %Q|{"node":{"label":"A malformed label"#{ invalid_tokens }}}| + put "/api/nodes/#{ node.id }", params: json_payload, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(400) + end + + end + + describe 'DELETE /api/nodes/:id' do + + let(:node) { create(:node, label: 'Existing Node', project: current_project) } + let(:delete_node) { delete "/api/nodes/#{ node.id }", env: @env } + + it 'deletes a node' do + delete_node + expect(response.status).to eq(200) + + expect { current_project.nodes.find(node.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + let(:model) { node } + let(:submit_form) { delete_node } + include_examples 'creates an Activity', :destroy + end + end + +end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/notes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/notes_spec.rb new file mode 100644 index 000000000..7333ca823 --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/notes_spec.rb @@ -0,0 +1,319 @@ +require 'rails_helper' + +describe 'Notes API' do + + include_context 'project scoped API' + include_context 'https' + + let(:node) { create(:node, project: current_project) } + + context 'as unauthenticated user' do + [ + ['get', '/api/nodes/1/notes/'], + ['get', '/api/nodes/1/notes/1'], + ['post', '/api/nodes/1/notes/'], + ['put', '/api/nodes/1/notes/1'], + ['patch', '/api/nodes/1/notes/1'], + ['delete', '/api/nodes/1/notes/1'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + let(:category) { create(:category) } + + describe 'GET /api/nodes/:node_id/notes' do + before do + @notes = [ + create(:note, node: node, text: "#[Title]#\nNote 0\n\n#[foo]#\nbar"), + create(:note, node: node, text: "#[Title]#\nNote 1\n\n#[uno]#\none"), + create(:note, node: node, text: "#[Title]#\nNote 2\n\n#[dos]#\ntwo"), + ] + create_list(:note, 30, node: node) + @other_note = create( + :note, node: create(:node, project: current_project), text: "#[Title]#\nOther Note" + ) + get "/api/nodes/#{node.id}/notes?#{params}", env: @env + end + + let(:retrieved_notes) { JSON.parse(response.body) } + + context 'without params' do + let (:params) { '' } + + it 'responds with HTTP code 200' do + expect(response.status).to eq(200) + end + + it 'retrieves all the notes for the given node' do + expect(retrieved_notes.count).to eq 33 + retrieved_titles = retrieved_notes.map { |json| json['title'] } + expect(retrieved_titles).to match_array(@notes.map(&:title)) + end + + it 'includes fields' do + note_0 = retrieved_notes.detect { |n| n['title'] == 'Note 0' } + note_1 = retrieved_notes.detect { |n| n['title'] == 'Note 1' } + note_2 = retrieved_notes.detect { |n| n['title'] == 'Note 2' } + + expect(note_0['fields'].keys).to match_array %w[Title foo] + expect(note_0['fields']['foo']).to eq 'bar' + expect(note_1['fields'].keys).to match_array %w[Title uno] + expect(note_1['fields']['uno']).to eq 'one' + expect(note_2['fields'].keys).to match_array %w[Title dos] + expect(note_2['fields']['dos']).to eq 'two' + end + + it 'doesn\'t return notes from other nodes' do + retrieved_ids = retrieved_notes.map { |n| n['id'] } + expect(retrieved_ids).not_to include @other_note.id + end + end + + context 'with params' do + let (:params) { 'page=2' } + + it 'retrieves the paginated notes for the given node' do + expect(retrieved_notes.count).to eq 8 + end + end + end + + describe 'GET /api/nodes/:node_id/notes/:id' do + before do + @note = node.notes.create!( + text: "#[Title]#\nMy note\n#[foo]#\nbar\n#[fizz]#\nbuzz", + category: category, + ) + get "/api/nodes/#{node.id}/notes/#{@note.id}", env: @env + end + + it 'responds with HTTP code 200' do + expect(response.status).to eq 200 + end + + it 'returns JSON information about the note' do + retrieved_note = JSON.parse(response.body) + expect(retrieved_note['id']).to eq @note.id + expect(retrieved_note['title']).to eq 'My note' + expect(retrieved_note['category_id']).to eq category.id + expect(retrieved_note['author']).to eq @note.author + expect(retrieved_note['fields'].keys).to match_array( + %w[foo fizz Title] + ) + expect(retrieved_note['fields']['foo']).to eq 'bar' + expect(retrieved_note['fields']['fizz']).to eq 'buzz' + end + end + + describe 'POST /api/nodes/:node_id/notes' do + let(:url) { "/api/nodes/#{node.id}/notes" } + let(:post_note) { post url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid note' do + let(:params) { { note: { text: 'New note' } } } + + it 'responds with HTTP code 201' do + post_note + expect(response.status).to eq 201 + end + + let(:submit_form) { post_note } + include_examples 'creates an Activity', :create, Note + include_examples 'sets the whodunnit', :create, Note + + context 'specifying a category' do + before { params[:note][:category_id] = category.id } + + it 'creates a note with the given node & category' do + expect { post_note }.to change { node.notes.count }.by(1) + note = node.notes.last + expect(note.category).to eq category + end + + it 'returns the attributes of the new note as JSON' do + post_note + retrieved_note = JSON.parse(response.body) + params[:note].each do |attr, value| + expect(retrieved_note[attr.to_s]).to eq value + end + expect(response.location).to eq( + dradis_api.node_note_path(node.id, retrieved_note['id']) + ) + end + end + + context 'and category is not specified' do + it 'creates a note with the given node & default category' do + expect { post_note }.to change { node.notes.count }.by(1) + expect(node.notes.last.category).to eq Category.default + end + end + end + + context 'with params for an invalid note' do + let(:params) { { note: { text: 'a' * 65536 } } } # too long + + it 'responds with HTTP code 422' do + post_note + expect(response.status).to eq 422 + end + + it 'doesn\'t create a note' do + expect { post_note }.not_to change { Note.count } + end + end + + context 'when no :note param is sent' do + let(:params) { {} } + + it 'doesn\'t create a note' do + expect { post_note }.not_to change { Note.count } + end + + it 'responds with HTTP code 422' do + post_note + expect(response.status).to eq(422) + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"note":{"label":"A malformed label", , }}' + post url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + it 'responds with HTTP code 415' do + params = { note: {} } + post url, params: params, env: @env + expect(response.status).to eq(415) + end + end + end + + describe 'PUT /api/nodes/:node_id/notes/:id' do + let(:note) do + create(:note, node: node, text: 'My text') + end + + let(:url) { "/api/nodes/#{node.id}/notes/#{note.id}" } + let(:put_note) { put url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid note' do + let(:params) { { note: { text: 'New text' } } } + + it 'responds with HTTP code 200' do + put_note + expect(response.status).to eq 200 + end + + it 'updates the note' do + put_note + expect(note.reload.text).to eq 'New text' + end + + let(:submit_form) { put_note } + let(:model) { note } + include_examples 'creates an Activity', :update + include_examples 'sets the whodunnit', :update + + it 'returns the attributes of the updated note as JSON' do + put_note + retrieved_note = JSON.parse(response.body) + expect(retrieved_note['text']).to eq 'New text' + end + end + + context 'with params for an invalid note' do + let(:params) { { note: { text: 'a' * 65536 } } } # too long + + it 'responds with HTTP code 422' do + put_note + expect(response.status).to eq 422 + end + + it 'doesn\'t update the note' do + expect { put_note }.not_to change { note.reload.attributes } + end + end + + context 'when no :note param is sent' do + let(:params) { {} } + + it 'doesn\'t update the note' do + expect { put_note }.not_to change { note.reload.attributes } + end + + it 'responds with HTTP code 422' do + put_note + expect(response.status).to eq 422 + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"note":{"label":"A malformed label", , }}' + put url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + let(:params) { { note: { text: 'New Note' } } } + + it 'responds with HTTP code 415' do + expect { put url, params: params, env: @env }.not_to change { note.reload.attributes } + expect(response.status).to eq 415 + end + end + end + + describe 'DELETE /api/nodes/:node_id/notes/:id' do + let(:note) { create(:note, node: node, text: 'My Note') } + + let(:delete_note) do + delete "/api/nodes/#{node.id}/notes/#{note.id}", env: @env + end + + it 'deletes the note' do + note_id = note.id + delete_note + expect(Note.find_by_id(note_id)).to be_nil + end + + it 'responds with error code 200' do + delete_note + expect(response.status).to eq(200) + end + + let(:submit_form) { delete_note } + let(:model) { note } + include_examples 'creates an Activity', :destroy + + it 'returns JSON with a success message' do + delete_note + parsed_response = JSON.parse(response.body) + expect(parsed_response['message']).to eq\ + 'Resource deleted successfully' + end + end + end +end From 06bc5496739b73a380d0115a146642706e981258 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 14:34:59 +0100 Subject: [PATCH 02/18] dradis-api - bump module version to V3 --- .../app/controllers/dradis/ce/api/v3/attachments_controller.rb | 2 +- .../app/controllers/dradis/ce/api/v3/evidence_controller.rb | 2 +- .../app/controllers/dradis/ce/api/v3/issues_controller.rb | 2 +- .../app/controllers/dradis/ce/api/v3/nodes_controller.rb | 2 +- .../app/controllers/dradis/ce/api/v3/notes_controller.rb | 2 +- .../spec/requests/dradis/ce/api/v1/attachments_spec.rb | 1 + .../dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb | 1 + .../dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb | 2 ++ engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb | 2 ++ engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb | 1 + 10 files changed, 12 insertions(+), 5 deletions(-) diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb index f5401278e..651894c4c 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb @@ -1,5 +1,5 @@ module Dradis::CE::API - module V1 + module V3 class AttachmentsController < Dradis::CE::API::APIController include ActivityTracking include Dradis::CE::API::ProjectScoped diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb index f321f0e02..308d71493 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb @@ -1,5 +1,5 @@ module Dradis::CE::API - module V1 + module V3 class EvidenceController < Dradis::CE::API::APIController include ActivityTracking include Dradis::CE::API::ProjectScoped diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb index 7ace96442..3141b9861 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/issues_controller.rb @@ -1,5 +1,5 @@ module Dradis::CE::API - module V1 + module V3 class IssuesController < Dradis::CE::API::APIController include ActivityTracking include Dradis::CE::API::ProjectScoped diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb index 1914835d6..e4c176a86 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/nodes_controller.rb @@ -1,5 +1,5 @@ module Dradis::CE::API - module V1 + module V3 class NodesController < Dradis::CE::API::APIController include ActivityTracking include Dradis::CE::API::ProjectScoped diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb index 10cf48a8a..9c9c1e153 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb @@ -1,5 +1,5 @@ module Dradis::CE::API - module V1 + module V3 class NotesController < Dradis::CE::API::APIController include ActivityTracking include Dradis::CE::API::ProjectScoped diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb index 726135288..40e3aa9e3 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb @@ -4,6 +4,7 @@ include_context "project scoped API" include_context "https" + let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } context "as unauthenticated user" do diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb index 712fe81f3..874d957ca 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb @@ -5,6 +5,7 @@ include_context 'project scoped API' include_context 'https' + let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } let(:issue) { create(:issue, node: current_project.issue_library) } diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb index 51744626a..d5bb2fc30 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb @@ -5,6 +5,8 @@ include_context 'project scoped API' include_context 'https' + let(:api_version) { 1 } + context 'as unauthenticated user' do [ ['get', '/api/issues/'], diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb index bc69fc7ef..ddb7fbd97 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb @@ -5,6 +5,8 @@ include_context 'project scoped API' include_context 'https' + let(:api_version) { 1 } + context 'as unauthenticated user' do [ ['get', '/api/nodes/'], diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb index 7333ca823..7e33aae28 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb @@ -5,6 +5,7 @@ include_context 'project scoped API' include_context 'https' + let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } context 'as unauthenticated user' do From aa3f65e6728fef313e85d2d53865962e9407ef48 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 15:04:03 +0100 Subject: [PATCH 03/18] dradis-api - update instructions --- engines/dradis-api/CHANGELOG | 2 +- engines/dradis-api/README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/engines/dradis-api/CHANGELOG b/engines/dradis-api/CHANGELOG index 88de0c0fa..0c13ed97d 100644 --- a/engines/dradis-api/CHANGELOG +++ b/engines/dradis-api/CHANGELOG @@ -1,2 +1,2 @@ -v2 (Aug 2023) +v3 (Aug 2023) - Add Boards, Lists, Cards endpoints. diff --git a/engines/dradis-api/README.md b/engines/dradis-api/README.md index ae3182e68..41fadded4 100644 --- a/engines/dradis-api/README.md +++ b/engines/dradis-api/README.md @@ -19,7 +19,8 @@ comments in the v1 controllers/ files for guidance on what to include in the deprecated files. - You'll also need to duplicate the routes block, and update the :default route constraint to point to the new version. -- You'll need to duplicate the request specs too. +- You'll need to duplicate the request specs too. Update the previous specs w/ +a `let(:api_version)` block. - Update the engine's CHANGELOG w/ a list of the breaking changes. From cade0930cfd79ab1871802a841732e0fc23585c8 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 15:04:44 +0100 Subject: [PATCH 04/18] dradis-api - initial implementation of /boards and /lists endpoints --- .../dradis/ce/api/v3/boards_controller.rb | 51 +++++++++++++++++ .../dradis/ce/api/v3/lists_controller.rb | 57 +++++++++++++++++++ .../ce/api/v3/boards/_board.json.jbuilder | 5 ++ .../ce/api/v3/boards/create.json.jbuilder | 1 + .../ce/api/v3/boards/index.json.jbuilder | 1 + .../ce/api/v3/boards/show.json.jbuilder | 1 + .../ce/api/v3/boards/update.json.jbuilder | 1 + .../ce/api/v3/cards/_card.json.jbuilder | 3 + .../ce/api/v3/cards/create.json.jbuilder | 1 + .../ce/api/v3/cards/index.json.jbuilder | 1 + .../dradis/ce/api/v3/cards/show.json.jbuilder | 1 + .../ce/api/v3/cards/update.json.jbuilder | 1 + .../ce/api/v3/lists/_list.json.jbuilder | 5 ++ .../ce/api/v3/lists/create.json.jbuilder | 1 + .../ce/api/v3/lists/index.json.jbuilder | 1 + .../dradis/ce/api/v3/lists/show.json.jbuilder | 1 + .../ce/api/v3/lists/update.json.jbuilder | 1 + 17 files changed, 133 insertions(+) create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/boards/_board.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/boards/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/boards/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/boards/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/boards/update.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/cards/_card.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/cards/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/cards/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/cards/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/cards/update.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/lists/_list.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/lists/create.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/lists/index.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/lists/show.json.jbuilder create mode 100644 engines/dradis-api/app/views/dradis/ce/api/v3/lists/update.json.jbuilder diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb new file mode 100644 index 000000000..9ad5a9cff --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb @@ -0,0 +1,51 @@ +module Dradis::CE::API + module V3 + class BoardsController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + def index + @boards = current_project.boards.includes(:lists, lists: [:cards]).order('updated_at desc') + @boards = @boards.page(params[:page].to_i) if params[:page] + end + + def show + @board = current_project.boards.includes(:lists, lists: [:cards]).find(params[:id]) + end + + def create + @board = current_project.boards.new(board_params) + + if @board.save + track_created(@board) + render status: 201, location: dradis_api.board_url(@board) + else + render_validation_errors(@board) + end + end + + def update + @board = current_project.boards.find(params[:id]) + if @board.update(board_params) + track_updated(@nboard) + render board: @board + else + render_validation_errors(@board) + end + end + + def destroy + board = current_project.boards.find(params[:id]) + board.destroy + track_destroyed(destroy) + render_successful_destroy_message + end + + protected + + def board_params + params.require(:board).permit(:name, :node_id) + end + end + end +end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb new file mode 100644 index 000000000..6a24a0eeb --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb @@ -0,0 +1,57 @@ +module Dradis::CE::API + module V3 + class ListsController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + before_action :set_board + + def index + @lists = @board.lists.order('updated_at desc') + @lists = @lists.page(params[:page].to_i) if params[:page] + end + + def show + @list = @board.lists.find(params[:id]) + end + + def create + @list = @board.lists.build(list_params) + if @list.save + track_created(@list) + render status: 201, location: board_list_path(@board, @list) + else + render_validation_errors(@list) + end + end + + def update + @list = @node.lists.find(params[:id]) + if @list.update(list_params) + track_updated(@list) + render list: @list + else + render_validation_errors(@list) + end + end + + def destroy + @list = @board.lists.find(params[:id]) + @list.destroy + track_destroyed(@list) + render_successful_destroy_message + end + + private + + def set_board + @board = current_project.boards.find(params[:board_id]) + end + + def list_params + params.require(:list).permit(:name) + end + + end + end +end diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/boards/_board.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/_board.json.jbuilder new file mode 100644 index 000000000..b4087edbf --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/_board.json.jbuilder @@ -0,0 +1,5 @@ +json.(board, :id, :name, :node_id, :created_at, :updated_at) + +json.lists board.lists do |list| + json.partial! list +end diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/boards/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/create.json.jbuilder new file mode 100644 index 000000000..61f42ee5d --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'board', board: @board diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/boards/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/index.json.jbuilder new file mode 100644 index 000000000..d6bd623fd --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @boards, partial: 'board', as: :board diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/boards/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/show.json.jbuilder new file mode 100644 index 000000000..61f42ee5d --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'board', board: @board diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/boards/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/update.json.jbuilder new file mode 100644 index 000000000..61f42ee5d --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/boards/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'board', board: @board diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/cards/_card.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/_card.json.jbuilder new file mode 100644 index 000000000..4832cbdb8 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/_card.json.jbuilder @@ -0,0 +1,3 @@ +json.(card, :id, :description, :due_date, :name, :fields) + +json.assignees card.assignees, :id, :email diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/cards/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/create.json.jbuilder new file mode 100644 index 000000000..1d3e1ddfb --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'card', card: @card diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/cards/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/index.json.jbuilder new file mode 100644 index 000000000..94b005399 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @cards, partial: 'card', as: :card diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/cards/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/show.json.jbuilder new file mode 100644 index 000000000..1d3e1ddfb --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'card', card: @card diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/cards/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/update.json.jbuilder new file mode 100644 index 000000000..1d3e1ddfb --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/cards/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'card', card: @card diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/lists/_list.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/_list.json.jbuilder new file mode 100644 index 000000000..31e3e113f --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/_list.json.jbuilder @@ -0,0 +1,5 @@ +json.(list, :id, :name, :created_at, :updated_at) + +json.cards list.cards do |card| + json.partial! card +end diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/lists/create.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/create.json.jbuilder new file mode 100644 index 000000000..06b8bf001 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'list', list: @list diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/lists/index.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/index.json.jbuilder new file mode 100644 index 000000000..5e1be1f20 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @lists, partial: 'list', as: :list diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/lists/show.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/show.json.jbuilder new file mode 100644 index 000000000..06b8bf001 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'list', list: @list diff --git a/engines/dradis-api/app/views/dradis/ce/api/v3/lists/update.json.jbuilder b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/update.json.jbuilder new file mode 100644 index 000000000..06b8bf001 --- /dev/null +++ b/engines/dradis-api/app/views/dradis/ce/api/v3/lists/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'list', list: @list From 3633e2eaf020737a6c4dbb77b9d9c7617b0f82ec Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 15:07:21 +0100 Subject: [PATCH 05/18] CHANGELOG - methodologies API endpoints --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f25033875..0370f1e1c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,8 +19,7 @@ - [report type]: - [future tense verb] [reporting enhancement] - REST/JSON API enhancements: - - [API entity]: - - [future tense verb] [API enhancement] + - Boards, Lists, Cards: add initial implementation - Security Fixes: - High: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] - Medium: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] From 437660eec7bbf47c5d25e89246e4afbf63a58895 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:40:27 +0100 Subject: [PATCH 06/18] dradis-api Lists - reduce N+1 --- .../app/controllers/dradis/ce/api/v3/lists_controller.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb index 6a24a0eeb..71dacb7e3 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb @@ -7,16 +7,18 @@ class ListsController < Dradis::CE::API::APIController before_action :set_board def index - @lists = @board.lists.order('updated_at desc') + @lists = @board.lists.includes(:cards, cards: :assignees).order('updated_at desc') @lists = @lists.page(params[:page].to_i) if params[:page] end def show - @list = @board.lists.find(params[:id]) + @list = @board.lists.includes(:cards, cards: :assignees).find(params[:id]) end def create @list = @board.lists.build(list_params) + @list.previous_id = @board.last_list.try(:id) + if @list.save track_created(@list) render status: 201, location: board_list_path(@board, @list) @@ -45,7 +47,7 @@ def destroy private def set_board - @board = current_project.boards.find(params[:board_id]) + @board = current_project.boards.includes(:lists).find(params[:board_id]) end def list_params From 38226118af42daf6c773d282be6c675f2b579965 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:40:41 +0100 Subject: [PATCH 07/18] dradis-api - add CardsController --- .../dradis/ce/api/v3/cards_controller.rb | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb new file mode 100644 index 000000000..10c1fbd23 --- /dev/null +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb @@ -0,0 +1,65 @@ +module Dradis::CE::API + module V3 + class CardsController < Dradis::CE::API::APIController + include ActivityTracking + include Dradis::CE::API::ProjectScoped + + before_action :set_board + before_action :set_list + + def index + @cards = @list.cards.includes(:assignees).order('updated_at desc') + @cards = @cards.page(params[:page].to_i) if params[:page] + end + + def show + @card = @list.cards.includes(:assignees).find(params[:id]) + end + + def create + @card = @list.build(card_params) + # Set the new card as the last card of the list + @card.previous_id = @list.last_card.try(:id) + + if @card.save + track_created(@card) + render status: 201, location: board_list_card_path(@board, @list, @card) + else + render_validation_errors(@card) + end + end + + def update + @card = @list.find(params[:id]) + if @card.update(card_params) + track_updated(@card) + render list: @card + else + render_validation_errors(@card) + end + end + + def destroy + @card = @list.find(params[:id]) + @card.destroy + track_destroyed(@card) + render_successful_destroy_message + end + + private + + def set_board + @board = current_project.boards.includes(:lists).find(params[:board_id]) + end + + def set_list + @list = @board.lists.includes(:cards, cards: :assignees).find(params[:list_id]) + end + + def card_params + params.require(:card).permit(:name, :description, :due_date, assignee_ids: []) + end + + end + end +end From f083475e9186eb5349afc76f965370858f5c10ab Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:58:50 +0200 Subject: [PATCH 08/18] dradis-api Boards - initial implementation of req. specs --- .../dradis/ce/api/v3/boards_controller.rb | 4 +- .../requests/dradis/ce/api/v3/boards_spec.rb | 198 ++++++++++++++++++ 2 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb index 9ad5a9cff..2fc350452 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb @@ -27,7 +27,7 @@ def create def update @board = current_project.boards.find(params[:id]) if @board.update(board_params) - track_updated(@nboard) + track_updated(@board) render board: @board else render_validation_errors(@board) @@ -37,7 +37,7 @@ def update def destroy board = current_project.boards.find(params[:id]) board.destroy - track_destroyed(destroy) + track_destroyed(board) render_successful_destroy_message end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb new file mode 100644 index 000000000..0c8e07738 --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb @@ -0,0 +1,198 @@ +require 'rails_helper' + +describe 'Boards API' do + + include_context 'project scoped API' + include_context 'https' + + context 'as unauthenticated user' do + [ + ['get', '/api/boards/'], + ['get', '/api/boards/1'], + ['post', '/api/boards/'], + ['put', '/api/boards/1'], + ['patch', '/api/boards/1'], + ['delete', '/api/boards/1'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + describe 'GET /api/boards' do + before do + @boards = create_list(:board, 30, project: current_project).sort_by(&:updated_at) + @board_names = @boards.map(&:name) + + get "/api/boards?#{params}", env: @env + + expect(response.status).to eq(200) + @retrieved_boards = JSON.parse(response.body) + end + + context 'without params' do + let(:params) { '' } + + it 'retrieves all the boards' do + retrieved_board_names = @retrieved_boards.map{ |p| p['name'] } + + expect(@retrieved_boards.count).to eq(@boards.count) + expect(retrieved_board_names).to match_array(@board_names) + end + end + + context 'with params' do + let(:params) { 'page=2' } + + it 'retrieves the paginated boards' do + expect(@retrieved_boards.count).to eq(5) + end + end + end + + describe 'GET /api/boards/:id' do + it 'retrieves a specific board' do + board = create(:board, name: 'Existing Board', project: current_project) + + get "/api/boards/#{ board.id }", env: @env + expect(response.status).to eq(200) + + retrieved_board = JSON.parse(response.body) + expect(retrieved_board['name']).to eq board.name + end + end + + describe 'POST /api/boards', skip: true do + let!(:parent_node_id) { Project.new.plugin_parent_node.id } + let(:valid_post) do + post '/api/nodes', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + end + let(:valid_params) do + { + node: { + label: 'New Node', + type_id: Node::Types::HOST, + parent_id: parent_node_id, + position: 3 + } + } + end + + it 'creates a new node' do + expect{valid_post}.to change{Node.count}.by(1) + expect(response.status).to eq(201) + + retrieved_node = JSON.parse(response.body) + + expect(response.location).to eq(dradis_api.node_url(retrieved_node['id'])) + + valid_params[:node].each do |attr, value| + expect(retrieved_node[attr.to_s]).to eq value + end + end + + # Activity shared example was originally written for feature requests and + # expects a 'submit_form' let variable to be defined: + let(:submit_form) { valid_post } + include_examples 'creates an Activity', :create, Node + + it 'throws 415 unless JSON is sent' do + params = { node: { } } + post '/api/boards', params: params, env: @env + expect(response.status).to eq(415) + end + + it 'throws 422 if node is invalid' do + params = { node: { label: '' } } + post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 422 if no :node param is sent' do + params = { } + post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 400 if invalid JSON is sent' do + invalid_tokens = ', , ' + json_payload = %Q|{"node":{"label":"A malformed label"#{ invalid_tokens }}}| + post '/api/nodes', params: json_payload, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(400) + end + end + + describe 'PUT /api/boards/:id' do + + let(:board) { create(:board, name: 'Existing Board', project: current_project) } + + let(:valid_put) do + put "/api/boards/#{ board.id }", params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + end + let(:valid_params) { { board: { name: 'Updated Board' } } } + + it 'updates a board' do + valid_put + expect(response.status).to eq(200) + expect(current_project.boards.find(board.id).name).to eq valid_params[:board][:name] + retrieved_board = JSON.parse(response.body) + expect(retrieved_board['name']).to eq valid_params[:board][:name] + end + + let(:submit_form) { valid_put } + let(:model) { board } + include_examples 'creates an Activity', :update + + it 'throws 415 unless JSON is sent' do + params = { board: { name: 'Bad Board' } } + put "/api/boards/#{ board.id }", params: params, env: @env + expect(response.status).to eq(415) + end + + it 'throws 422 if board is invalid' do + params = { board: { name: '' } } + put "/api/boards/#{ board.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 422 if no :board param is sent' do + params = { } + put "/api/boards/#{ board.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(422) + end + + it 'throws 400 if invalid JSON is sent' do + invalid_tokens = ', , ' + json_payload = %Q|{"board":{"name":"A malformed name"#{ invalid_tokens }}}| + put "/api/boards/#{ board.id }", params: json_payload, env: @env.merge('CONTENT_TYPE' => 'application/json') + expect(response.status).to eq(400) + end + + end + + describe 'DELETE /api/boards/:id' do + + let(:board) { create(:board, name: 'Existing Board', project: current_project) } + let(:delete_board) { delete "/api/boards/#{ board.id }", env: @env } + + it 'deletes a board' do + delete_board + expect(response.status).to eq(200) + + expect { current_project.boards.find(board.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + let(:model) { board } + let(:submit_form) { delete_board } + include_examples 'creates an Activity', :destroy + end + end + +end From 7bc3cb28d77087ad8cdc74e2a7ff63aa0457de21 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Sun, 13 Aug 2023 16:15:21 +0200 Subject: [PATCH 09/18] dradis-api Boards - complete specs --- .../requests/dradis/ce/api/v3/boards_spec.rb | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb index 0c8e07738..464a1e3bc 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb @@ -69,62 +69,59 @@ end end - describe 'POST /api/boards', skip: true do - let!(:parent_node_id) { Project.new.plugin_parent_node.id } + describe 'POST /api/boards' do let(:valid_post) do - post '/api/nodes', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + post '/api/boards', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') end let(:valid_params) do { - node: { - label: 'New Node', - type_id: Node::Types::HOST, - parent_id: parent_node_id, - position: 3 + board: { + name: 'New Board', + node_id: current_project.methodology_library.id } } end - it 'creates a new node' do - expect{valid_post}.to change{Node.count}.by(1) + it 'creates a new board' do + expect{valid_post}.to change{Board.count}.by(1) expect(response.status).to eq(201) - retrieved_node = JSON.parse(response.body) + retrieved_board = JSON.parse(response.body) - expect(response.location).to eq(dradis_api.node_url(retrieved_node['id'])) + expect(response.location).to eq(dradis_api.board_url(retrieved_board['id'])) - valid_params[:node].each do |attr, value| - expect(retrieved_node[attr.to_s]).to eq value + valid_params[:board].each do |attr, value| + expect(retrieved_board[attr.to_s]).to eq value end end # Activity shared example was originally written for feature requests and # expects a 'submit_form' let variable to be defined: let(:submit_form) { valid_post } - include_examples 'creates an Activity', :create, Node + include_examples 'creates an Activity', :create, Board it 'throws 415 unless JSON is sent' do - params = { node: { } } + params = { board: { } } post '/api/boards', params: params, env: @env expect(response.status).to eq(415) end - it 'throws 422 if node is invalid' do - params = { node: { label: '' } } - post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + it 'throws 422 if board is invalid' do + params = { board: { name: '' } } + post '/api/boards', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end - it 'throws 422 if no :node param is sent' do + it 'throws 422 if no :board param is sent' do params = { } - post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') + post '/api/boards', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end it 'throws 400 if invalid JSON is sent' do invalid_tokens = ', , ' - json_payload = %Q|{"node":{"label":"A malformed label"#{ invalid_tokens }}}| - post '/api/nodes', params: json_payload, env: @env.merge('CONTENT_TYPE' => 'application/json') + json_payload = %Q|{"board":{"name":"A malformed name"#{ invalid_tokens }}}| + post '/api/boards', params: json_payload, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(400) end end From edab2a01cdc4f5319ae47b9594634a4a6b8b82a9 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Mon, 14 Aug 2023 17:08:39 +0200 Subject: [PATCH 10/18] dradis-api - add Cards req. specs --- app/models/card.rb | 3 +- .../dradis/ce/api/v3/cards_controller.rb | 6 +- .../requests/dradis/ce/api/v3/cards_spec.rb | 306 ++++++++++++++++++ 3 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb diff --git a/app/models/card.rb b/app/models/card.rb index 4f044a277..b0dd2ac55 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -84,7 +84,6 @@ def to_xml(xml_builder, includes: [], version: 3) end end - private def local_fields { 'List' => list.name.parameterize(preserve_case: true, separator: '_'), @@ -92,6 +91,8 @@ def local_fields } end + private + # We are saving the board_id to the card's version so that if the card's list # is deleted, we still have an idea if the card's board still exists. def add_board_id_to_version diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb index 10c1fbd23..e5641c0a6 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb @@ -17,7 +17,7 @@ def show end def create - @card = @list.build(card_params) + @card = @list.cards.build(card_params) # Set the new card as the last card of the list @card.previous_id = @list.last_card.try(:id) @@ -30,7 +30,7 @@ def create end def update - @card = @list.find(params[:id]) + @card = @list.cards.find(params[:id]) if @card.update(card_params) track_updated(@card) render list: @card @@ -40,7 +40,7 @@ def update end def destroy - @card = @list.find(params[:id]) + @card = @list.cards.find(params[:id]) @card.destroy track_destroyed(@card) render_successful_destroy_message diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb new file mode 100644 index 000000000..1c0424238 --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb @@ -0,0 +1,306 @@ +require 'rails_helper' + +describe 'Cards API' do + + include_context 'project scoped API' + include_context 'https' + + let(:board) { create(:board, project: current_project) } + let(:list) { create(:list, board: board) } + + context 'as unauthenticated user' do + [ + ['get', '/api/boards/1/lists/1/cards'], + ['get', '/api/boards/1/lists/1/cards/1'], + ['post', '/api/boards/1/lists/1/cards'], + ['put', '/api/boards/1/lists/1/cards/1'], + ['patch', '/api/boards/1/lists/1/cards/1'], + ['delete', '/api/boards/1/lists/1/cards/1'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + describe 'GET /api/boards/:node_id/evidence', pending: true do + before do + @issues = create_list(:issue, 4, node: current_project.issue_library) + @evidence = [ + Evidence.create!(node: node, content: "#[a]#\nA", issue: @issues[0]), + Evidence.create!(node: node, content: "#[b]#\nB", issue: @issues[1]), + Evidence.create!(node: node, content: "#[c]#\nC", issue: @issues[2]), + ] << create_list(:evidence, 30, issue: @issues[3], node: node) + @other_evidence = create(:evidence, issue: issue, node: current_project.issue_library) + get "/api/nodes/#{node.id}/evidence?#{params}", env: @env + end + + let(:retrieved_evidence) { JSON.parse(response.body) } + + context 'without params' do + let(:params) { '' } + + it 'responds with HTTP code 200' do + expect(response.status).to eq(200) + end + + it 'retrieves all the evidence for the given node' do + expect(retrieved_evidence.count).to eq 33 + issue_titles = retrieved_evidence.map { |json| json['issue']['title'] }.uniq + expect(issue_titles).to match_array @issues.map(&:title) + end + + it 'returns JSON data about the evidence\'s fields and issue' do + ev_0 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[0].id } + ev_1 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[1].id } + ev_2 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[2].id } + + expect(ev_0['fields'].keys).to \ + match_array (@evidence[0].local_fields.keys << 'a') + expect(ev_0['fields']['a']).to eq 'A' + expect(ev_0['issue']['title']).to eq @issues[0].title + expect(ev_1['fields'].keys).to \ + match_array (@evidence[2].local_fields.keys << 'b') + expect(ev_1['fields']['b']).to eq 'B' + expect(ev_1['issue']['title']).to eq @issues[1].title + expect(ev_2['fields'].keys).to \ + match_array (@evidence[2].local_fields.keys << 'c') + expect(ev_2['fields']['c']).to eq 'C' + expect(ev_2['issue']['title']).to eq @issues[2].title + end + + it 'doesn\'t return evidence from other nodes' do + retrieved_ids = retrieved_evidence.map { |n| n['id'] } + expect(retrieved_ids).not_to include @other_evidence.id + end + end + + context 'with params' do + let(:params) { 'page=2' } + + it 'returns the paginated evidence' do + expect(retrieved_evidence.count).to eq 8 + + end + end + end + + describe 'GET /api/boards/:board_id/lists/:list_id/cards/:id' do + before do + @card = list.cards.create!( + description: "#[foo]#\nbar\n#[fizz]#\nbuzz", + name: 'My rspec card', + ) + get "/api/boards/#{board.id}/lists/#{list.id}/cards/#{@card.id}", env: @env + end + + it 'responds with HTTP code 200' do + expect(response.status).to eq 200 + end + + it 'returns JSON information about the card' do + retrieved_card = JSON.parse(response.body) + expect(retrieved_card['id']).to eq @card.id + expect(retrieved_card['name']).to eq @card.name + expect(retrieved_card['fields'].keys).to match_array( + @card.local_fields.keys + %w(fizz foo) + ) + expect(retrieved_card['fields']['foo']).to eq 'bar' + expect(retrieved_card['fields']['fizz']).to eq 'buzz' + end + end + + describe 'POST /api/boards/:board_id/lists/:list_id/cards' do + let(:url) { "/api/boards/#{board.id}/lists/#{list.id}/cards" } + let(:post_card) { post url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid evidence' do + let(:params) { { card: { description: 'New description', name: 'New name' } } } + + it 'responds with HTTP code 201' do + post_card + expect(response.status).to eq 201 + end + + it 'creates an card' do + expect { post_card }.to change { list.cards.count } + new_card = list.cards.last + expect(new_card.description).to eq 'New description' + expect(new_card.name).to eq 'New name' + end + + let(:submit_form) { post_card } + include_examples 'creates an Activity', :create, Card + include_examples 'sets the whodunnit', :create, Card + end + + context 'with params for an invalid evidence' do + let(:params) { { card: { description: 'New card' } } } # no name or list + + it 'responds with HTTP code 422' do + post_card + expect(response.status).to eq 422 + end + + it "doesn't create a card" do + expect { post_card }.not_to change { Card.count } + end + end + + context 'when no :card param is sent' do + let(:params) { {} } + + it "doesn't create an evidence" do + expect { post_card }.not_to change { Card.count } + end + + it 'responds with HTTP code 422' do + post_card + expect(response.status).to eq(422) + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"card":{"name":"A malformed name", , }}' + post url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + it 'responds with HTTP code 415' do + params = { card: {} } + post url, params: params, env: @env + expect(response.status).to eq(415) + end + end + end + + describe 'PUT /api/boards/:board_id/lists/:list_id/cards/:id' do + let(:card) do + create(:card, list: list, description: 'My description') + end + + let(:url) { "/api/boards/#{board.id}/lists/#{list.id}/cards/#{card.id}" } + let(:put_card) { put url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid card' do + let(:params) { { card: { description: 'New description' } } } + + it 'responds with HTTP code 200' do + put_card + expect(response.status).to eq 200 + end + + it 'updates the evidence' do + put_card + expect(card.reload.description).to eq 'New description' + end + + it 'returns the attributes of the updated evidence as JSON' do + put_card + retrieved_card = JSON.parse(response.body) + expect(retrieved_card['description']).to eq 'New description' + end + + let(:submit_form) { put_card } + let(:model) { card } + include_examples 'creates an Activity', :update + include_examples 'sets the whodunnit', :update + end + + context 'with params for an invalid card' do + let(:params) { { card: { description: 'a' * 65536 } } } # too long + + it 'responds with HTTP code 422' do + put_card + expect(response.status).to eq 422 + end + + it "doesn't update the evidence" do + expect { put_card }.not_to change { card.reload.attributes } + end + end + + context 'when no :card param is sent' do + let(:params) { {} } + + it "doesn't update the card" do + expect { put_card }.not_to change { card.reload.attributes } + end + + it 'responds with HTTP code 422' do + put_card + expect(response.status).to eq 422 + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"card":{"name":"A malformed name", , }}' + put url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + let(:params) { { card: { description: 'New Card' } } } + + it 'responds with HTTP code 415' do + expect { put url, params: params, env: @env }.not_to change { card.reload.attributes } + expect(response.status).to eq 415 + end + end + end + + describe 'DELETE /api/boards/:board_id/lists/:list_id/cards/:id' do + # the Card model adds Board info to PaperTrail, which by default is + # disabled during :testing + before { PaperTrail.enabled = true } + after { PaperTrail.enabled = false } + + let(:card) { create(:card, list: list, description: 'My Card') } + + let(:delete_card) do + delete "/api/boards/#{board.id}/lists/#{list.id}/cards/#{card.id}", env: @env + end + + it 'deletes the card' do + card_id = card.id + delete_card + expect(Card.find_by_id(card_id)).to be_nil + end + + it 'responds with error code 200' do + delete_card + expect(response.status).to eq(200) + end + + it 'returns JSON with a success message' do + delete_card + parsed_response = JSON.parse(response.body) + expect(parsed_response['message']).to eq\ + 'Resource deleted successfully' + end + + let(:submit_form) { delete_card } + let(:model) { card } + include_examples 'creates an Activity', :destroy + end + end +end From e1c9f8c463ed1d9c32c135dff688e3000b6453cf Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Tue, 15 Aug 2023 10:54:08 +0200 Subject: [PATCH 11/18] dradis-api - add Cards specs --- .../dradis/ce/api/v1/evidence_spec.rb | 2 +- .../requests/dradis/ce/api/v3/cards_spec.rb | 54 +++++++++---------- .../dradis/ce/api/v3/evidence_spec.rb | 2 +- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb index 874d957ca..9d3493033 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb @@ -39,7 +39,7 @@ Evidence.create!(node: node, content: "#[a]#\nA", issue: @issues[0]), Evidence.create!(node: node, content: "#[b]#\nB", issue: @issues[1]), Evidence.create!(node: node, content: "#[c]#\nC", issue: @issues[2]), - ] << create_list(:evidence, 30, issue: @issues[3], node: node) + ] + create_list(:evidence, 30, issue: @issues[3], node: node) @other_evidence = create(:evidence, issue: issue, node: current_project.issue_library) get "/api/nodes/#{node.id}/evidence?#{params}", env: @env end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb index 1c0424238..9f847d71f 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb @@ -29,19 +29,18 @@ context 'as authorized user' do include_context 'authorized API user' - describe 'GET /api/boards/:node_id/evidence', pending: true do + describe 'GET /api/boards/:board_id/lists/:list_id/cards' do before do - @issues = create_list(:issue, 4, node: current_project.issue_library) - @evidence = [ - Evidence.create!(node: node, content: "#[a]#\nA", issue: @issues[0]), - Evidence.create!(node: node, content: "#[b]#\nB", issue: @issues[1]), - Evidence.create!(node: node, content: "#[c]#\nC", issue: @issues[2]), - ] << create_list(:evidence, 30, issue: @issues[3], node: node) - @other_evidence = create(:evidence, issue: issue, node: current_project.issue_library) - get "/api/nodes/#{node.id}/evidence?#{params}", env: @env + @cards = [ + Card.create!(list: list, description: "#[a]#\nA", name: 'Card A'), + Card.create!(list: list, description: "#[b]#\nB", name: 'Card B'), + Card.create!(list: list, description: "#[c]#\nC", name: 'Card C'), + ] + create_list(:card, 30, list: list) + @other_card = create(:card, list: create(:list, board: board)) + get "/api/boards/#{board.id}/lists/#{list.id}/cards?#{params}", env: @env end - let(:retrieved_evidence) { JSON.parse(response.body) } + let(:retrieved_cards) { JSON.parse(response.body) } context 'without params' do let(:params) { '' } @@ -50,34 +49,33 @@ expect(response.status).to eq(200) end - it 'retrieves all the evidence for the given node' do - expect(retrieved_evidence.count).to eq 33 - issue_titles = retrieved_evidence.map { |json| json['issue']['title'] }.uniq - expect(issue_titles).to match_array @issues.map(&:title) + it 'retrieves all the cards for the given list' do + expect(retrieved_cards.count).to eq 33 + card_names = retrieved_cards.map { |json| json['name'] } + expect(card_names).to match_array @cards.map(&:name) end - it 'returns JSON data about the evidence\'s fields and issue' do - ev_0 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[0].id } - ev_1 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[1].id } - ev_2 = retrieved_evidence.find { |n| n['issue']['id'] == @issues[2].id } + it 'returns JSON data about the cards\'s fields' do + ev_0 = retrieved_cards.find { |n| n['id'] == @cards[0].id } + ev_1 = retrieved_cards.find { |n| n['id'] == @cards[1].id } + ev_2 = retrieved_cards.find { |n| n['id'] == @cards[2].id } expect(ev_0['fields'].keys).to \ - match_array (@evidence[0].local_fields.keys << 'a') + match_array (@cards[0].local_fields.keys << 'a') expect(ev_0['fields']['a']).to eq 'A' - expect(ev_0['issue']['title']).to eq @issues[0].title + expect(ev_1['fields'].keys).to \ - match_array (@evidence[2].local_fields.keys << 'b') + match_array (@cards[2].local_fields.keys << 'b') expect(ev_1['fields']['b']).to eq 'B' - expect(ev_1['issue']['title']).to eq @issues[1].title + expect(ev_2['fields'].keys).to \ - match_array (@evidence[2].local_fields.keys << 'c') + match_array (@cards[2].local_fields.keys << 'c') expect(ev_2['fields']['c']).to eq 'C' - expect(ev_2['issue']['title']).to eq @issues[2].title end - it 'doesn\'t return evidence from other nodes' do - retrieved_ids = retrieved_evidence.map { |n| n['id'] } - expect(retrieved_ids).not_to include @other_evidence.id + it 'doesn\'t return cards from other lists' do + retrieved_ids = retrieved_cards.map { |n| n['id'] } + expect(retrieved_ids).not_to include @other_card.id end end @@ -85,7 +83,7 @@ let(:params) { 'page=2' } it 'returns the paginated evidence' do - expect(retrieved_evidence.count).to eq 8 + expect(retrieved_cards.count).to eq 8 end end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb index 712fe81f3..81049940a 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/evidence_spec.rb @@ -38,7 +38,7 @@ Evidence.create!(node: node, content: "#[a]#\nA", issue: @issues[0]), Evidence.create!(node: node, content: "#[b]#\nB", issue: @issues[1]), Evidence.create!(node: node, content: "#[c]#\nC", issue: @issues[2]), - ] << create_list(:evidence, 30, issue: @issues[3], node: node) + ] + create_list(:evidence, 30, issue: @issues[3], node: node) @other_evidence = create(:evidence, issue: issue, node: current_project.issue_library) get "/api/nodes/#{node.id}/evidence?#{params}", env: @env end From 54101104d5936d86bf2e2c5b7db56f1020a77dc5 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Tue, 15 Aug 2023 11:02:22 +0200 Subject: [PATCH 12/18] dradis-api - initial pass for List specs --- .../requests/dradis/ce/api/v3/lists_spec.rb | 297 ++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb new file mode 100644 index 000000000..34643a787 --- /dev/null +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb @@ -0,0 +1,297 @@ +require 'rails_helper' + +describe 'Lists API' do + + include_context 'project scoped API' + include_context 'https' + + let(:board) { create(:board, project: current_project) } + + context 'as unauthenticated user' do + [ + ['get', '/api/boards/1/lists'], + ['get', '/api/boards/1/lists/1'], + ['post', '/api/boards/1/lists'], + ['put', '/api/boards/1/lists/1'], + ['patch', '/api/boards/1/lists/1'], + ['delete', '/api/boards/1/lists/1'], + ].each do |verb, url| + describe "#{verb.upcase} #{url}" do + it 'throws 401' do + send(verb, url, params: {}, env: @env) + expect(response.status).to eq 401 + end + end + end + end + + context 'as authorized user' do + include_context 'authorized API user' + + describe 'GET /api/boards/:board_id/lists/:list_id/cards', pending: true do + before do + @cards = [ + Card.create!(list: list, description: "#[a]#\nA", name: 'Card A'), + Card.create!(list: list, description: "#[b]#\nB", name: 'Card B'), + Card.create!(list: list, description: "#[c]#\nC", name: 'Card C'), + ] + create_list(:card, 30, list: list) + @other_card = create(:card, list: create(:list, board: board)) + get "/api/boards/#{board.id}/lists/#{list.id}/cards?#{params}", env: @env + end + + let(:retrieved_cards) { JSON.parse(response.body) } + + context 'without params' do + let(:params) { '' } + + it 'responds with HTTP code 200' do + expect(response.status).to eq(200) + end + + it 'retrieves all the cards for the given list' do + expect(retrieved_cards.count).to eq 33 + card_names = retrieved_cards.map { |json| json['name'] } + expect(card_names).to match_array @cards.map(&:name) + end + + it 'returns JSON data about the cards\'s fields' do + ev_0 = retrieved_cards.find { |n| n['id'] == @cards[0].id } + ev_1 = retrieved_cards.find { |n| n['id'] == @cards[1].id } + ev_2 = retrieved_cards.find { |n| n['id'] == @cards[2].id } + + expect(ev_0['fields'].keys).to \ + match_array (@cards[0].local_fields.keys << 'a') + expect(ev_0['fields']['a']).to eq 'A' + + expect(ev_1['fields'].keys).to \ + match_array (@cards[2].local_fields.keys << 'b') + expect(ev_1['fields']['b']).to eq 'B' + + expect(ev_2['fields'].keys).to \ + match_array (@cards[2].local_fields.keys << 'c') + expect(ev_2['fields']['c']).to eq 'C' + end + + it 'doesn\'t return cards from other lists' do + retrieved_ids = retrieved_cards.map { |n| n['id'] } + expect(retrieved_ids).not_to include @other_card.id + end + end + + context 'with params' do + let(:params) { 'page=2' } + + it 'returns the paginated evidence' do + expect(retrieved_lists.count).to eq 8 + end + end + end + + describe 'GET /api/boards/:board_id/lists/:list_id/cards/:id', pending: true do + before do + @card = list.cards.create!( + description: "#[foo]#\nbar\n#[fizz]#\nbuzz", + name: 'My rspec card', + ) + get "/api/boards/#{board.id}/lists/#{list.id}/cards/#{@card.id}", env: @env + end + + it 'responds with HTTP code 200' do + expect(response.status).to eq 200 + end + + it 'returns JSON information about the card' do + retrieved_card = JSON.parse(response.body) + expect(retrieved_card['id']).to eq @card.id + expect(retrieved_card['name']).to eq @card.name + expect(retrieved_card['fields'].keys).to match_array( + @card.local_fields.keys + %w(fizz foo) + ) + expect(retrieved_card['fields']['foo']).to eq 'bar' + expect(retrieved_card['fields']['fizz']).to eq 'buzz' + end + end + + describe 'POST /api/boards/:board_id/lists/:list_id/cards', pending: true do + let(:url) { "/api/boards/#{board.id}/lists/#{list.id}/cards" } + let(:post_card) { post url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid evidence' do + let(:params) { { card: { description: 'New description', name: 'New name' } } } + + it 'responds with HTTP code 201' do + post_card + expect(response.status).to eq 201 + end + + it 'creates an card' do + expect { post_card }.to change { list.cards.count } + new_card = list.cards.last + expect(new_card.description).to eq 'New description' + expect(new_card.name).to eq 'New name' + end + + let(:submit_form) { post_card } + include_examples 'creates an Activity', :create, Card + include_examples 'sets the whodunnit', :create, Card + end + + context 'with params for an invalid evidence' do + let(:params) { { card: { description: 'New card' } } } # no name or list + + it 'responds with HTTP code 422' do + post_card + expect(response.status).to eq 422 + end + + it "doesn't create a card" do + expect { post_card }.not_to change { Card.count } + end + end + + context 'when no :card param is sent' do + let(:params) { {} } + + it "doesn't create an evidence" do + expect { post_card }.not_to change { Card.count } + end + + it 'responds with HTTP code 422' do + post_card + expect(response.status).to eq(422) + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"card":{"name":"A malformed name", , }}' + post url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + it 'responds with HTTP code 415' do + params = { card: {} } + post url, params: params, env: @env + expect(response.status).to eq(415) + end + end + end + + describe 'PUT /api/boards/:board_id/lists/:list_id/cards/:id', pending: true do + let(:card) do + create(:card, list: list, description: 'My description') + end + + let(:url) { "/api/boards/#{board.id}/lists/#{list.id}/cards/#{card.id}" } + let(:put_card) { put url, params: params.to_json, env: @env } + + context 'when content_type header = application/json' do + include_context 'content_type: application/json' + + context 'with params for a valid card' do + let(:params) { { card: { description: 'New description' } } } + + it 'responds with HTTP code 200' do + put_card + expect(response.status).to eq 200 + end + + it 'updates the evidence' do + put_card + expect(card.reload.description).to eq 'New description' + end + + it 'returns the attributes of the updated evidence as JSON' do + put_card + retrieved_card = JSON.parse(response.body) + expect(retrieved_card['description']).to eq 'New description' + end + + let(:submit_form) { put_card } + let(:model) { card } + include_examples 'creates an Activity', :update + include_examples 'sets the whodunnit', :update + end + + context 'with params for an invalid card' do + let(:params) { { card: { description: 'a' * 65536 } } } # too long + + it 'responds with HTTP code 422' do + put_card + expect(response.status).to eq 422 + end + + it "doesn't update the evidence" do + expect { put_card }.not_to change { card.reload.attributes } + end + end + + context 'when no :card param is sent' do + let(:params) { {} } + + it "doesn't update the card" do + expect { put_card }.not_to change { card.reload.attributes } + end + + it 'responds with HTTP code 422' do + put_card + expect(response.status).to eq 422 + end + end + + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do + json_payload = '{"card":{"name":"A malformed name", , }}' + put url, params: json_payload, env: @env + expect(response.status).to eq(400) + end + end + end + + context 'when JSON is not sent' do + let(:params) { { card: { description: 'New Card' } } } + + it 'responds with HTTP code 415' do + expect { put url, params: params, env: @env }.not_to change { card.reload.attributes } + expect(response.status).to eq 415 + end + end + end + + describe 'DELETE /api/boards/:board_id/lists/:id' do + let(:list) { create(:list, board: board) } + + let(:delete_list) do + delete "/api/boards/#{board.id}/lists/#{list.id}", env: @env + end + + it 'deletes the list' do + list_id = list.id + delete_list + expect(List.find_by_id(list_id)).to be_nil + end + + it 'responds with error code 200' do + delete_list + expect(response.status).to eq(200) + end + + it 'returns JSON with a success message' do + delete_list + parsed_response = JSON.parse(response.body) + expect(parsed_response['message']).to eq\ + 'Resource deleted successfully' + end + + let(:submit_form) { delete_list } + let(:model) { list } + include_examples 'creates an Activity', :destroy + end + end +end From 3e9d9b377a30e502fdcfe407a410c4e795d60190 Mon Sep 17 00:00:00 2001 From: Daniel Martin <53006+etdsoft@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:10:25 +0200 Subject: [PATCH 13/18] dradis-api - more List specs --- .../dradis/ce/api/v3/lists_controller.rb | 2 +- .../requests/dradis/ce/api/v3/cards_spec.rb | 14 +-- .../requests/dradis/ce/api/v3/lists_spec.rb | 108 +++++++++--------- 3 files changed, 59 insertions(+), 65 deletions(-) diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb index 71dacb7e3..ab8e90412 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb @@ -28,7 +28,7 @@ def create end def update - @list = @node.lists.find(params[:id]) + @list = @board.lists.find(params[:id]) if @list.update(list_params) track_updated(@list) render list: @list diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb index 9f847d71f..30ebb252e 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/cards_spec.rb @@ -82,7 +82,7 @@ context 'with params' do let(:params) { 'page=2' } - it 'returns the paginated evidence' do + it 'returns the paginated card' do expect(retrieved_cards.count).to eq 8 end @@ -121,7 +121,7 @@ context 'when content_type header = application/json' do include_context 'content_type: application/json' - context 'with params for a valid evidence' do + context 'with params for a valid card' do let(:params) { { card: { description: 'New description', name: 'New name' } } } it 'responds with HTTP code 201' do @@ -141,7 +141,7 @@ include_examples 'sets the whodunnit', :create, Card end - context 'with params for an invalid evidence' do + context 'with params for an invalid card' do let(:params) { { card: { description: 'New card' } } } # no name or list it 'responds with HTTP code 422' do @@ -157,7 +157,7 @@ context 'when no :card param is sent' do let(:params) { {} } - it "doesn't create an evidence" do + it "doesn't create an card" do expect { post_card }.not_to change { Card.count } end @@ -204,12 +204,12 @@ expect(response.status).to eq 200 end - it 'updates the evidence' do + it 'updates the card' do put_card expect(card.reload.description).to eq 'New description' end - it 'returns the attributes of the updated evidence as JSON' do + it 'returns the attributes of the updated card as JSON' do put_card retrieved_card = JSON.parse(response.body) expect(retrieved_card['description']).to eq 'New description' @@ -229,7 +229,7 @@ expect(response.status).to eq 422 end - it "doesn't update the evidence" do + it "doesn't update the card" do expect { put_card }.not_to change { card.reload.attributes } end end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb index 34643a787..0eddcce33 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb @@ -112,62 +112,60 @@ end end - describe 'POST /api/boards/:board_id/lists/:list_id/cards', pending: true do - let(:url) { "/api/boards/#{board.id}/lists/#{list.id}/cards" } - let(:post_card) { post url, params: params.to_json, env: @env } + describe 'POST /api/boards/:board_id/lists' do + let(:url) { "/api/boards/#{board.id}/lists" } + let(:post_list) { post url, params: params.to_json, env: @env } context 'when content_type header = application/json' do include_context 'content_type: application/json' - context 'with params for a valid evidence' do - let(:params) { { card: { description: 'New description', name: 'New name' } } } + context 'with params for a valid list' do + let(:params) { { list: { name: 'New name' } } } it 'responds with HTTP code 201' do - post_card + post_list expect(response.status).to eq 201 end - it 'creates an card' do - expect { post_card }.to change { list.cards.count } - new_card = list.cards.last - expect(new_card.description).to eq 'New description' - expect(new_card.name).to eq 'New name' + it 'creates an list' do + expect { post_list }.to change { board.lists.count } + new_list = board.lists.last + expect(new_list.name).to eq 'New name' end - let(:submit_form) { post_card } - include_examples 'creates an Activity', :create, Card - include_examples 'sets the whodunnit', :create, Card + let(:submit_form) { post_list } + include_examples 'creates an Activity', :create, List end - context 'with params for an invalid evidence' do - let(:params) { { card: { description: 'New card' } } } # no name or list + context 'with params for an invalid list' do + let(:params) { { list: { } } } # no name it 'responds with HTTP code 422' do - post_card + post_list expect(response.status).to eq 422 end - it "doesn't create a card" do - expect { post_card }.not_to change { Card.count } + it "doesn't create a list" do + expect { post_list }.not_to change { List.count } end end - context 'when no :card param is sent' do + context 'when no :list param is sent' do let(:params) { {} } - it "doesn't create an evidence" do - expect { post_card }.not_to change { Card.count } + it "doesn't create an list" do + expect { post_list }.not_to change { List.count } end it 'responds with HTTP code 422' do - post_card + post_list expect(response.status).to eq(422) end end context 'when invalid JSON is sent' do it 'responds with HTTP code 400' do - json_payload = '{"card":{"name":"A malformed name", , }}' + json_payload = '{"list":{"name":"A malformed name", , }}' post url, params: json_payload, env: @env expect(response.status).to eq(400) end @@ -176,78 +174,74 @@ context 'when JSON is not sent' do it 'responds with HTTP code 415' do - params = { card: {} } + params = { list: {} } post url, params: params, env: @env expect(response.status).to eq(415) end end end - describe 'PUT /api/boards/:board_id/lists/:list_id/cards/:id', pending: true do - let(:card) do - create(:card, list: list, description: 'My description') - end - - let(:url) { "/api/boards/#{board.id}/lists/#{list.id}/cards/#{card.id}" } - let(:put_card) { put url, params: params.to_json, env: @env } + describe 'PUT /api/boards/:board_id/lists/:id' do + let(:list) { create(:list, board: board) } + let(:url) { "/api/boards/#{board.id}/lists/#{list.id}" } + let(:put_list) { put url, params: params.to_json, env: @env } context 'when content_type header = application/json' do include_context 'content_type: application/json' - context 'with params for a valid card' do - let(:params) { { card: { description: 'New description' } } } + context 'with params for a valid list' do + let(:params) { { list: { name: 'New name' } } } it 'responds with HTTP code 200' do - put_card + put_list expect(response.status).to eq 200 end - it 'updates the evidence' do - put_card - expect(card.reload.description).to eq 'New description' + it 'updates the list' do + put_list + expect(list.reload.name).to eq 'New name' end - it 'returns the attributes of the updated evidence as JSON' do - put_card - retrieved_card = JSON.parse(response.body) - expect(retrieved_card['description']).to eq 'New description' + it 'returns the attributes of the updated list as JSON' do + put_list + retrieved_list = JSON.parse(response.body) + expect(retrieved_list['name']).to eq 'New name' end - let(:submit_form) { put_card } - let(:model) { card } + let(:submit_form) { put_list } + let(:model) { list } include_examples 'creates an Activity', :update - include_examples 'sets the whodunnit', :update end - context 'with params for an invalid card' do - let(:params) { { card: { description: 'a' * 65536 } } } # too long + context 'with params for an invalid list' do + let(:params) { { list: { name: 'a' * 65536 } } } # too long it 'responds with HTTP code 422' do - put_card + put_list expect(response.status).to eq 422 end - it "doesn't update the evidence" do - expect { put_card }.not_to change { card.reload.attributes } + it "doesn't update the list" do + expect { put_list }.not_to change { list.reload.attributes } end end - context 'when no :card param is sent' do + context 'when no :list param is sent' do let(:params) { {} } - it "doesn't update the card" do - expect { put_card }.not_to change { card.reload.attributes } + it "doesn't update the list" do + expect { put_list }.not_to change { list.reload.attributes } end it 'responds with HTTP code 422' do - put_card + put_list expect(response.status).to eq 422 end end context 'when invalid JSON is sent' do it 'responds with HTTP code 400' do - json_payload = '{"card":{"name":"A malformed name", , }}' + json_payload = '{"list":{"name":"A malformed name", , }}' put url, params: json_payload, env: @env expect(response.status).to eq(400) end @@ -255,10 +249,10 @@ end context 'when JSON is not sent' do - let(:params) { { card: { description: 'New Card' } } } + let(:params) { { list: { name: 'New List' } } } it 'responds with HTTP code 415' do - expect { put url, params: params, env: @env }.not_to change { card.reload.attributes } + expect { put url, params: params, env: @env }.not_to change { list.reload.attributes } expect(response.status).to eq 415 end end From 38de7731e60df76e5e226a32d9496786fdb274b6 Mon Sep 17 00:00:00 2001 From: Caitlin Date: Tue, 15 Aug 2023 11:46:59 -0400 Subject: [PATCH 14/18] finish api v3 lists_spec --- .../requests/dradis/ce/api/v3/lists_spec.rb | 74 +++++++------------ spec/factories/lists.rb | 2 +- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb index 0eddcce33..f95971026 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb @@ -28,18 +28,14 @@ context 'as authorized user' do include_context 'authorized API user' - describe 'GET /api/boards/:board_id/lists/:list_id/cards', pending: true do + describe 'GET /api/boards/:board_id/lists' do before do - @cards = [ - Card.create!(list: list, description: "#[a]#\nA", name: 'Card A'), - Card.create!(list: list, description: "#[b]#\nB", name: 'Card B'), - Card.create!(list: list, description: "#[c]#\nC", name: 'Card C'), - ] + create_list(:card, 30, list: list) - @other_card = create(:card, list: create(:list, board: board)) - get "/api/boards/#{board.id}/lists/#{list.id}/cards?#{params}", env: @env + @lists = create_list(:list, 30, board: board) + @other_list = create(:list, board: create(:board, project: current_project)) + get "/api/boards/#{board.id}/lists?#{params}", env: @env end - let(:retrieved_cards) { JSON.parse(response.body) } + let(:retrieved_lists) { JSON.parse(response.body) } context 'without params' do let(:params) { '' } @@ -48,33 +44,25 @@ expect(response.status).to eq(200) end - it 'retrieves all the cards for the given list' do - expect(retrieved_cards.count).to eq 33 - card_names = retrieved_cards.map { |json| json['name'] } - expect(card_names).to match_array @cards.map(&:name) + it 'retrieves all the lists for the given board' do + expect(retrieved_lists.count).to eq 30 + list_names = retrieved_lists.map { |json| json['name'] } + expect(list_names).to match_array @lists.map(&:name) end - it 'returns JSON data about the cards\'s fields' do - ev_0 = retrieved_cards.find { |n| n['id'] == @cards[0].id } - ev_1 = retrieved_cards.find { |n| n['id'] == @cards[1].id } - ev_2 = retrieved_cards.find { |n| n['id'] == @cards[2].id } + it 'returns JSON data about the lists' do + li_0 = retrieved_lists.find { |n| n['id'] == @lists[0].id } + li_1 = retrieved_lists.find { |n| n['id'] == @lists[1].id } + li_2 = retrieved_lists.find { |n| n['id'] == @lists[2].id } - expect(ev_0['fields'].keys).to \ - match_array (@cards[0].local_fields.keys << 'a') - expect(ev_0['fields']['a']).to eq 'A' - - expect(ev_1['fields'].keys).to \ - match_array (@cards[2].local_fields.keys << 'b') - expect(ev_1['fields']['b']).to eq 'B' - - expect(ev_2['fields'].keys).to \ - match_array (@cards[2].local_fields.keys << 'c') - expect(ev_2['fields']['c']).to eq 'C' + expect(li_0['name']).to eq(@lists[0].name) + expect(li_1['name']).to eq(@lists[1].name) + expect(li_2['name']).to eq(@lists[2].name) end - it 'doesn\'t return cards from other lists' do - retrieved_ids = retrieved_cards.map { |n| n['id'] } - expect(retrieved_ids).not_to include @other_card.id + it 'doesn\'t return lists from other boards' do + retrieved_ids = retrieved_lists.map { |n| n['id'] } + expect(retrieved_ids).not_to include @other_list.id end end @@ -82,33 +70,25 @@ let(:params) { 'page=2' } it 'returns the paginated evidence' do - expect(retrieved_lists.count).to eq 8 + expect(retrieved_lists.count).to eq 5 end end end - describe 'GET /api/boards/:board_id/lists/:list_id/cards/:id', pending: true do + describe 'GET /api/boards/:board_id/lists/:list_id' do before do - @card = list.cards.create!( - description: "#[foo]#\nbar\n#[fizz]#\nbuzz", - name: 'My rspec card', - ) - get "/api/boards/#{board.id}/lists/#{list.id}/cards/#{@card.id}", env: @env + @list = create(:list, board: board) + get "/api/boards/#{board.id}/lists/#{@list.id}", env: @env end it 'responds with HTTP code 200' do expect(response.status).to eq 200 end - it 'returns JSON information about the card' do - retrieved_card = JSON.parse(response.body) - expect(retrieved_card['id']).to eq @card.id - expect(retrieved_card['name']).to eq @card.name - expect(retrieved_card['fields'].keys).to match_array( - @card.local_fields.keys + %w(fizz foo) - ) - expect(retrieved_card['fields']['foo']).to eq 'bar' - expect(retrieved_card['fields']['fizz']).to eq 'buzz' + it 'returns JSON information about the list' do + retrieved_list = JSON.parse(response.body) + expect(retrieved_list['id']).to eq @list.id + expect(retrieved_list['name']).to eq @list.name end end diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb index f5d45092f..17064b8b2 100644 --- a/spec/factories/lists.rb +++ b/spec/factories/lists.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :list do - sequence(:name){ |n| "List-#{n}" } + sequence(:name) { |n| "List-#{n}" } association :board end end From 9ceee7eb7db7e66b80643fc9b56705ec654eb2fe Mon Sep 17 00:00:00 2001 From: Caitlin Date: Fri, 18 Aug 2023 10:41:08 -0400 Subject: [PATCH 15/18] add brakeman ignore --- config/brakeman.ignore | 49 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 010681c10..b54ccba39 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -69,6 +69,29 @@ ], "note": "False positive: Attachment.pwd is set by the admin to specify the directory for the attachments" }, + { + "warning_type": "File Access", + "warning_code": 16, + "fingerprint": "180c4b532ac14a974f17a0884b71b2dfd1bfe01815b4c84ae070f082842a49fc", + "check_name": "FileAccess", + "message": "Model attribute used in file name", + "file": "engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb", + "line": 56, + "link": "https://brakemanscanner.org/docs/warning_types/file_access/", + "code": "File.rename(Attachment.find(params[:filename], :conditions => ({ :node_id => current_project.nodes.find(params[:node_id]).id })).fullpath, Attachment.pwd.join(current_project.nodes.find(params[:node_id]).id.to_s, CGI.unescape(attachment_params[:filename])).to_s)", + "render_path": null, + "location": { + "type": "method", + "class": "Dradis::CE::API::V3::AttachmentsController", + "method": "update" + }, + "user_input": "Attachment.find(params[:filename], :conditions => ({ :node_id => current_project.nodes.find(params[:node_id]).id })).fullpath", + "confidence": "Medium", + "cwe_id": [ + 22 + ], + "note": "False positive: The destination filename is prepended by the Attachments directory and validated as such to prevent being moved to the other directories" + }, { "warning_type": "File Access", "warning_code": 16, @@ -209,6 +232,30 @@ ], "note": "False positive: params[:uploader] here is being validated in the controller" }, + { + "warning_type": "Denial of Service", + "warning_code": 76, + "fingerprint": "d4b56fe0de40fbaed1bfdd6cc44d57ac2dc2b4aef5293c2afc326aef6e0c88e6", + "check_name": "RegexDoS", + "message": "Model attribute used in regular expression", + "file": "engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb", + "line": 55, + "link": "https://brakemanscanner.org/docs/warning_types/denial_of_service/", + "code": "/^#{Attachment.pwd}/", + "render_path": null, + "location": { + "type": "method", + "class": "Dradis::CE::API::V3::AttachmentsController", + "method": "update" + }, + "user_input": "Attachment.pwd", + "confidence": "Medium", + "cwe_id": [ + 20, + 185 + ], + "note": "False positive: Attachment.pwd is set by the admin to specify the directory for the attachments" + }, { "warning_type": "Remote Code Execution", "warning_code": 24, @@ -267,6 +314,6 @@ "note": "False positive: The params is used to fetch the boards and cannot be manipulated by user input" } ], - "updated": "2023-03-30 20:21:19 +0800", + "updated": "2023-08-18 10:40:16 -0400", "brakeman_version": "5.4.0" } From 07555775a5422a30cc438c52f84a53c4cc1e3f49 Mon Sep 17 00:00:00 2001 From: Caitlin Date: Fri, 18 Aug 2023 13:12:03 -0400 Subject: [PATCH 16/18] dradis-api - include versioned_api in specs and update boards controller create action --- engines/dradis-api/README.md | 2 +- .../app/controllers/dradis/ce/api/v3/boards_controller.rb | 2 ++ .../spec/requests/dradis/ce/api/v1/attachments_spec.rb | 1 + .../dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb | 1 + .../dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb | 1 + engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb | 1 + engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb | 1 + 7 files changed, 8 insertions(+), 1 deletion(-) diff --git a/engines/dradis-api/README.md b/engines/dradis-api/README.md index 41fadded4..d1ebd2cfc 100644 --- a/engines/dradis-api/README.md +++ b/engines/dradis-api/README.md @@ -20,7 +20,7 @@ deprecated files. - You'll also need to duplicate the routes block, and update the :default route constraint to point to the new version. - You'll need to duplicate the request specs too. Update the previous specs w/ -a `let(:api_version)` block. +a `let(:api_version)` block and `include_context 'versioned API'` - Update the engine's CHANGELOG w/ a list of the breaking changes. diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb index 2fc350452..4a88aff48 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/boards_controller.rb @@ -15,6 +15,8 @@ def show def create @board = current_project.boards.new(board_params) + # we are mimicking the hidden_field used in the UI to set the node_id in CE + @board.node_id = current_project.methodology_library.id if !params[:node_id] if @board.save track_created(@board) diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb index 40e3aa9e3..ab5617939 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb @@ -3,6 +3,7 @@ describe "Attachments API" do include_context "project scoped API" include_context "https" + include_context 'versioned API' let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb index 9d3493033..0b2e15774 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/evidence_spec.rb @@ -4,6 +4,7 @@ include_context 'project scoped API' include_context 'https' + include_context 'versioned API' let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb index d5bb2fc30..fa8f01f34 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/issues_spec.rb @@ -4,6 +4,7 @@ include_context 'project scoped API' include_context 'https' + include_context 'versioned API' let(:api_version) { 1 } diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb index ddb7fbd97..f0c84851e 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb @@ -4,6 +4,7 @@ include_context 'project scoped API' include_context 'https' + include_context 'versioned API' let(:api_version) { 1 } diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb index 7e33aae28..1720367a7 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/notes_spec.rb @@ -4,6 +4,7 @@ include_context 'project scoped API' include_context 'https' + include_context 'versioned API' let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } From 10538393bd11fda67a4d745b19cbcc9af4d7bec4 Mon Sep 17 00:00:00 2001 From: Caitlin Date: Fri, 18 Aug 2023 13:30:05 -0400 Subject: [PATCH 17/18] dradis-api - lint --- .../dradis/ce/api/v1/evidence_controller.rb | 1 - .../dradis/ce/api/v1/notes_controller.rb | 1 - .../ce/api/v3/attachments_controller.rb | 12 +- .../dradis/ce/api/v3/cards_controller.rb | 1 - .../dradis/ce/api/v3/evidence_controller.rb | 1 - .../dradis/ce/api/v3/lists_controller.rb | 1 - .../dradis/ce/api/v3/notes_controller.rb | 1 - engines/dradis-api/config/routes.rb | 2 +- .../dradis/ce/api/v1/attachments_spec.rb | 158 +++++++++--------- .../requests/dradis/ce/api/v1/nodes_spec.rb | 10 +- .../dradis/ce/api/v3/attachments_spec.rb | 158 +++++++++--------- .../requests/dradis/ce/api/v3/boards_spec.rb | 10 +- .../requests/dradis/ce/api/v3/lists_spec.rb | 2 +- .../requests/dradis/ce/api/v3/nodes_spec.rb | 10 +- 14 files changed, 181 insertions(+), 187 deletions(-) diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v1/evidence_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v1/evidence_controller.rb index f321f0e02..022b81208 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v1/evidence_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v1/evidence_controller.rb @@ -51,7 +51,6 @@ def set_node def evidence_params params.require(:evidence).permit(:content, :issue_id) end - end end end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v1/notes_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v1/notes_controller.rb index 10cf48a8a..b70e2a990 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v1/notes_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v1/notes_controller.rb @@ -52,7 +52,6 @@ def set_node def note_params params.require(:note).permit(:category_id, :text) end - end end end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb index 651894c4c..679433a81 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/attachments_controller.rb @@ -6,7 +6,7 @@ class AttachmentsController < Dradis::CE::API::APIController before_action :set_node - skip_before_action :json_required, :only => [:create] + skip_before_action :json_required, only: [:create] def index @attachments = @node.attachments.each(&:close) @@ -14,7 +14,7 @@ def index def show begin - @attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id } ) + @attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id }) rescue raise ActiveRecord::RecordNotFound, "Couldn't find attachment with filename '#{params[:filename]}'" end @@ -45,7 +45,7 @@ def create end def update - attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id } ) + attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id }) attachment.close begin @@ -54,9 +54,9 @@ def update if !File.exist?(destination) && !destination.match(/^#{Attachment.pwd}/).nil? File.rename attachment.fullpath, destination - @attachment = Attachment.find(new_name, conditions: { node_id: @node.id } ) + @attachment = Attachment.find(new_name, conditions: { node_id: @node.id }) else - raise "Destination file already exists" + raise 'Destination file already exists' end rescue @attachment = attachment @@ -65,7 +65,7 @@ def update end def destroy - @attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id} ) + @attachment = Attachment.find(params[:filename], conditions: { node_id: @node.id }) @attachment.delete render_successful_destroy_message diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb index e5641c0a6..5f1662442 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/cards_controller.rb @@ -59,7 +59,6 @@ def set_list def card_params params.require(:card).permit(:name, :description, :due_date, assignee_ids: []) end - end end end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb index 308d71493..d78f403a5 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/evidence_controller.rb @@ -51,7 +51,6 @@ def set_node def evidence_params params.require(:evidence).permit(:content, :issue_id) end - end end end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb index ab8e90412..0db794ec3 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/lists_controller.rb @@ -53,7 +53,6 @@ def set_board def list_params params.require(:list).permit(:name) end - end end end diff --git a/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb b/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb index 9c9c1e153..f2fa63ee1 100644 --- a/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb +++ b/engines/dradis-api/app/controllers/dradis/ce/api/v3/notes_controller.rb @@ -52,7 +52,6 @@ def set_node def note_params params.require(:note).permit(:category_id, :text) end - end end end diff --git a/engines/dradis-api/config/routes.rb b/engines/dradis-api/config/routes.rb index f635edbe0..fda6dfd22 100644 --- a/engines/dradis-api/config/routes.rb +++ b/engines/dradis-api/config/routes.rb @@ -6,7 +6,7 @@ resources :nodes do resources :evidence resources :notes - constraints(:filename => /.*/) do + constraints(filename: /.*/) do resources :attachments, param: :filename end end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb index ab5617939..91fcf6fd5 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/attachments_spec.rb @@ -1,14 +1,14 @@ require 'rails_helper' -describe "Attachments API" do - include_context "project scoped API" - include_context "https" +describe 'Attachments API' do + include_context 'project scoped API' + include_context 'https' include_context 'versioned API' let(:api_version) { 1 } let(:node) { create(:node, project: current_project) } - context "as unauthenticated user" do + context 'as unauthenticated user' do [ ['get', '/api/nodes/1/attachments/'], ['get', '/api/nodes/1/attachments/image.jpg'], @@ -26,8 +26,8 @@ end end - context "as authorized user" do - include_context "authorized API user" + context 'as authorized user' do + include_context 'authorized API user' before(:each) do FileUtils.rm_rf Dir[Attachment.pwd.join('*')] until Dir[Attachment.pwd.join('*')].count == 0 @@ -37,82 +37,82 @@ FileUtils.rm_rf Dir[Attachment.pwd.join('*')] end - describe "GET /api/nodes/:node_id/attachments" do + describe 'GET /api/nodes/:node_id/attachments' do before do - @attachments = ["image0.png", "image1.png", "image2.png"] + @attachments = ['image0.png', 'image1.png', 'image2.png'] @attachments.each do |attachment| create(:attachment, filename: attachment, node: node) end # an attachment in another node - create(:attachment, filename: "image3.png", node: create(:node, project: current_project)) + create(:attachment, filename: 'image3.png', node: create(:node, project: current_project)) get "/api/nodes/#{node.id}/attachments", env: @env end let(:retrieved_attachments) { JSON.parse(response.body) } - it "responds with HTTP code 200" do + it 'responds with HTTP code 200' do expect(response.status).to eq(200) end - it "retrieves all the attachments for the given node" do + it 'retrieves all the attachments for the given node' do expect(retrieved_attachments.count).to eq @attachments.count - retrieved_filenames = retrieved_attachments.map{ |json| json['filename'] } + retrieved_filenames = retrieved_attachments.map { |json| json['filename'] } expect(retrieved_filenames).to match_array(@attachments) end - it "returns JSON information about attachments" do - attachment_0 = retrieved_attachments.detect { |n| n["filename"] == "image0.png" } - attachment_1 = retrieved_attachments.detect { |n| n["filename"] == "image1.png" } - attachment_2 = retrieved_attachments.detect { |n| n["filename"] == "image2.png" } + it 'returns JSON information about attachments' do + attachment_0 = retrieved_attachments.detect { |n| n['filename'] == 'image0.png' } + attachment_1 = retrieved_attachments.detect { |n| n['filename'] == 'image1.png' } + attachment_2 = retrieved_attachments.detect { |n| n['filename'] == 'image2.png' } expect(attachment_0).to eq({ - "filename" => "image0.png", - "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image0.png" + 'filename' => 'image0.png', + 'link' => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image0.png" }) expect(attachment_1).to eq({ - "filename" => "image1.png", - "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image1.png" + 'filename' => 'image1.png', + 'link' => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image1.png" }) expect(attachment_2).to eq({ - "filename" => "image2.png", - "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image2.png" + 'filename' => 'image2.png', + 'link' => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image2.png" }) end - it "doesn't return attachments from other nodes" do - expect(retrieved_attachments.map{ |json| json['filename'] }).not_to include "image3.png" + it 'doesn\'t return attachments from other nodes' do + expect(retrieved_attachments.map { |json| json['filename'] }).not_to include 'image3.png' end end - describe "GET /api/nodes/:node_id/attachments/:filename" do + describe 'GET /api/nodes/:node_id/attachments/:filename' do before do - create(:attachment, filename: "image.png", node: node) + create(:attachment, filename: 'image.png', node: node) get "/api/nodes/#{node.id}/attachments/image.png", env: @env end - it "responds with HTTP code 200" do + it 'responds with HTTP code 200' do expect(response.status).to eq 200 end - it "responds with HTTP code 404 when not found" do + it 'responds with HTTP code 404 when not found' do get "/api/nodes/#{node.id}/attachments/image_ko.png", env: @env expect(response.status).to eq 404 json_response = JSON.parse(response.body) - expect(json_response["message"]).to eq "Couldn't find attachment with filename 'image_ko.png'" + expect(json_response['message']).to eq "Couldn't find attachment with filename 'image_ko.png'" end - it "returns JSON information about the attachment" do + it 'returns JSON information about the attachment' do retrieved_attachment = JSON.parse(response.body) expect(retrieved_attachment.keys).to match_array(%w[filename link]) - expect(retrieved_attachment["filename"]).to eq "image.png" - expect(retrieved_attachment["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image.png" + expect(retrieved_attachment['filename']).to eq 'image.png' + expect(retrieved_attachment['link']).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image.png" end end - describe "POST /api/nodes/:node_id/attachments" do + describe 'POST /api/nodes/:node_id/attachments' do let(:post_attachment) { file = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) params = { files: [file] } @@ -121,13 +121,13 @@ post url , params: params, env: @env } - it "returns 201 when file saved" do + it 'returns 201 when file saved' do post_attachment expect(response.status).to eq 201 expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'rails.png'))).to be true end - it "returns 422 when no file saved" do + it 'returns 422 when no file saved' do url = "/api/nodes/#{node.id}/attachments" post url , params: {}, env: @env @@ -135,11 +135,11 @@ expect(File.exist?(Attachment.pwd.join(node.id.to_s))).to be false end - it "auto-renames the upload if an attachment with the same name already exists" do + it 'auto-renames the upload if an attachment with the same name already exists' do node_attachments = Attachment.pwd.join(node.id.to_s) - FileUtils.mkdir_p( node_attachments ) + FileUtils.mkdir_p(node_attachments) - create(:attachment, filename: "rails.png", node: node) + create(:attachment, filename: 'rails.png', node: node) expect(Dir["#{node_attachments}/*"].count).to eq(1) post_attachment @@ -147,7 +147,7 @@ expect(Dir["#{node_attachments}/*"].count).to eq(2) end - it "returns JSON information about the attachments" do + it 'returns JSON information about the attachments' do file1 = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) file2 = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) params = { files: [file1, file2] } @@ -157,96 +157,96 @@ retrieved_attachments = JSON.parse(response.body) - attachment_0 = retrieved_attachments.detect { |n| n["filename"] == "rails.png" } - attachment_1 = retrieved_attachments.detect { |n| n["filename"] == "rails_copy-01.png" } + attachment_0 = retrieved_attachments.detect { |n| n['filename'] == 'rails.png' } + attachment_1 = retrieved_attachments.detect { |n| n['filename'] == 'rails_copy-01.png' } expect(attachment_0.keys).to match_array %w[filename link] - expect(attachment_0["filename"]).to eq "rails.png" - expect(attachment_0["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails.png" + expect(attachment_0['filename']).to eq 'rails.png' + expect(attachment_0['link']).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails.png" expect(attachment_1.keys).to match_array %w[filename link] - expect(attachment_1["filename"]).to eq "rails_copy-01.png" - expect(attachment_1["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails_copy-01.png" + expect(attachment_1['filename']).to eq 'rails_copy-01.png' + expect(attachment_1['link']).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails_copy-01.png" end end - describe "PUT /api/nodes/:node_id/attachments/:filename" do + describe 'PUT /api/nodes/:node_id/attachments/:filename' do before do - create(:attachment, filename: "image.png", node: node) + create(:attachment, filename: 'image.png', node: node) end let(:url) { "/api/nodes/#{node.id}/attachments/image.png" } let(:put_attachment) { put url, params: params.to_json, env: @env } - context "when content_type header = application/json" do - include_context "content_type: application/json" + context 'when content_type header = application/json' do + include_context 'content_type: application/json' - context "with params for a valid attachment" do - let(:params) { { attachment: { filename: "image_renamed.png" } } } + context 'with params for a valid attachment' do + let(:params) { { attachment: { filename: 'image_renamed.png' } } } - it "responds with HTTP code 200 if attachment exists" do + it 'responds with HTTP code 200 if attachment exists' do put_attachment expect(response.status).to eq 200 end - it "responds with HTTP code 404 if attachemnt doesn't exist" do + it 'responds with HTTP code 404 if attachment doesn\'t exist' do bad_url = "/api/nodes/#{node.id}/attachments/image_ko.png" put bad_url, params: params, env: @env expect(response.status).to eq(400) end - it "updates the attachment" do + it 'updates the attachment' do put_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be false expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image_renamed.png'))).to be true end - it "returns the attributes of the updated attachment as JSON" do + it 'returns the attributes of the updated attachment as JSON' do put_attachment retrieved_attachment = JSON.parse(response.body) - expect(retrieved_attachment["filename"]).to eq "image_renamed.png" + expect(retrieved_attachment['filename']).to eq 'image_renamed.png' end - it "responds with HTTP code 422 if attachment already exists" do - create(:attachment, filename: "image_renamed.png", node: node) + it 'responds with HTTP code 422 if attachment already exists' do + create(:attachment, filename: 'image_renamed.png', node: node) put_attachment expect(response.status).to eq(422) retrieved_attachment = JSON.parse(response.body) - expect(retrieved_attachment["filename"]).to eq "image.png" + expect(retrieved_attachment['filename']).to eq 'image.png' end end - context "with params for an invalid attachment" do - let(:params) { { attachment: { filename: "a"*65536 } } } # too long + context 'with params for an invalid attachment' do + let(:params) { { attachment: { filename: 'a' * 65536 } } } # too long - it "responds with HTTP code 422" do + it 'responds with HTTP code 422' do put_attachment expect(response.status).to eq 422 end - it "doesn't update the attachment" do + it 'doesn\'t update the attachment' do put_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image_renamed.png'))).to be false end end - context "when no :attachment param is sent" do + context 'when no :attachment param is sent' do let(:params) { {} } - it "doesn't update the attachment" do + it 'doesn\'t update the attachment' do put_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true end - it "responds with HTTP code 422" do + it 'responds with HTTP code 422' do put_attachment expect(response.status).to eq 422 end end - context "when invalid JSON is sent" do - it "responds with HTTP code 400" do + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do json_payload = '{"attachment":{"filename":"A malformed label", , }}' put url, params: json_payload, env: @env expect(response.status).to eq(400) @@ -254,10 +254,10 @@ end end - context "when JSON is not sent" do - let(:params) { { attachment: { filename: "image_renamed.jpg" } } } + context 'when JSON is not sent' do + let(:params) { { attachment: { filename: 'image_renamed.jpg' } } } - it "responds with HTTP code 415" do + it 'responds with HTTP code 415' do put url, params: params, env: @env expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true expect(response.status).to eq 415 @@ -265,30 +265,30 @@ end end - describe "DELETE /api/nodes/:node_id/attachments/:filename" do - let(:attachment) { "image.png" } + describe 'DELETE /api/nodes/:node_id/attachments/:filename' do + let(:attachment) { 'image.png' } let(:delete_attachment) do create(:attachment, filename: attachment, node: node) delete "/api/nodes/#{node.id}/attachments/#{attachment}", env: @env end - it "deletes the attachment" do + it 'deletes the attachment' do delete_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, attachment))).to\ - be false + be false end - it "responds with error code 200" do + it 'responds with error code 200' do delete_attachment expect(response.status).to eq(200) end - it "returns JSON with a success message" do + it 'returns JSON with a success message' do delete_attachment parsed_response = JSON.parse(response.body) - expect(parsed_response["message"]).to eq\ - "Resource deleted successfully" + expect(parsed_response['message']).to eq\ + 'Resource deleted successfully' end end end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb index f0c84851e..5f4c89f81 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v1/nodes_spec.rb @@ -44,7 +44,7 @@ let(:params) { '' } it 'retrieves all the nodes' do - retrieved_node_labels = @retrieved_nodes.map{ |p| p['label'] } + retrieved_node_labels = @retrieved_nodes.map { |p| p['label'] } expect(@retrieved_nodes.count).to eq(@nodes.count) expect(retrieved_node_labels).to match_array(@node_labels) @@ -89,7 +89,7 @@ end it 'creates a new node' do - expect{valid_post}.to change{Node.count}.by(1) + expect { valid_post }.to change { Node.count }.by(1) expect(response.status).to eq(201) retrieved_node = JSON.parse(response.body) @@ -107,7 +107,7 @@ include_examples 'creates an Activity', :create, Node it 'throws 415 unless JSON is sent' do - params = { node: { } } + params = { node: {} } post '/api/nodes', params: params, env: @env expect(response.status).to eq(415) end @@ -119,7 +119,7 @@ end it 'throws 422 if no :node param is sent' do - params = { } + params = {} post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end @@ -173,7 +173,7 @@ end it 'throws 422 if no :node param is sent' do - params = { } + params = {} put "/api/nodes/#{ node.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb index 726135288..fc2b43625 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/attachments_spec.rb @@ -1,12 +1,12 @@ require 'rails_helper' -describe "Attachments API" do - include_context "project scoped API" - include_context "https" +describe 'Attachments API' do + include_context 'project scoped API' + include_context 'https' let(:node) { create(:node, project: current_project) } - context "as unauthenticated user" do + context 'as unauthenticated user' do [ ['get', '/api/nodes/1/attachments/'], ['get', '/api/nodes/1/attachments/image.jpg'], @@ -24,8 +24,8 @@ end end - context "as authorized user" do - include_context "authorized API user" + context 'as authorized user' do + include_context 'authorized API user' before(:each) do FileUtils.rm_rf Dir[Attachment.pwd.join('*')] until Dir[Attachment.pwd.join('*')].count == 0 @@ -35,82 +35,82 @@ FileUtils.rm_rf Dir[Attachment.pwd.join('*')] end - describe "GET /api/nodes/:node_id/attachments" do + describe 'GET /api/nodes/:node_id/attachments' do before do - @attachments = ["image0.png", "image1.png", "image2.png"] + @attachments = ['image0.png', 'image1.png', 'image2.png'] @attachments.each do |attachment| create(:attachment, filename: attachment, node: node) end # an attachment in another node - create(:attachment, filename: "image3.png", node: create(:node, project: current_project)) + create(:attachment, filename: 'image3.png', node: create(:node, project: current_project)) get "/api/nodes/#{node.id}/attachments", env: @env end let(:retrieved_attachments) { JSON.parse(response.body) } - it "responds with HTTP code 200" do + it 'responds with HTTP code 200' do expect(response.status).to eq(200) end - it "retrieves all the attachments for the given node" do + it 'retrieves all the attachments for the given node' do expect(retrieved_attachments.count).to eq @attachments.count - retrieved_filenames = retrieved_attachments.map{ |json| json['filename'] } + retrieved_filenames = retrieved_attachments.map { |json| json['filename'] } expect(retrieved_filenames).to match_array(@attachments) end - it "returns JSON information about attachments" do - attachment_0 = retrieved_attachments.detect { |n| n["filename"] == "image0.png" } - attachment_1 = retrieved_attachments.detect { |n| n["filename"] == "image1.png" } - attachment_2 = retrieved_attachments.detect { |n| n["filename"] == "image2.png" } + it 'returns JSON information about attachments' do + attachment_0 = retrieved_attachments.detect { |n| n['filename'] == 'image0.png' } + attachment_1 = retrieved_attachments.detect { |n| n['filename'] == 'image1.png' } + attachment_2 = retrieved_attachments.detect { |n| n['filename'] == 'image2.png' } expect(attachment_0).to eq({ - "filename" => "image0.png", - "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image0.png" + 'filename' => 'image0.png', + 'link' => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image0.png" }) expect(attachment_1).to eq({ - "filename" => "image1.png", - "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image1.png" + 'filename' => 'image1.png', + 'link' => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image1.png" }) expect(attachment_2).to eq({ - "filename" => "image2.png", - "link" => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image2.png" + 'filename' => 'image2.png', + 'link' => "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image2.png" }) end - it "doesn't return attachments from other nodes" do - expect(retrieved_attachments.map{ |json| json['filename'] }).not_to include "image3.png" + it 'doesn\'t return attachments from other nodes' do + expect(retrieved_attachments.map { |json| json['filename'] }).not_to include 'image3.png' end end - describe "GET /api/nodes/:node_id/attachments/:filename" do + describe 'GET /api/nodes/:node_id/attachments/:filename' do before do - create(:attachment, filename: "image.png", node: node) + create(:attachment, filename: 'image.png', node: node) get "/api/nodes/#{node.id}/attachments/image.png", env: @env end - it "responds with HTTP code 200" do + it 'responds with HTTP code 200' do expect(response.status).to eq 200 end - it "responds with HTTP code 404 when not found" do + it 'responds with HTTP code 404 when not found' do get "/api/nodes/#{node.id}/attachments/image_ko.png", env: @env expect(response.status).to eq 404 json_response = JSON.parse(response.body) - expect(json_response["message"]).to eq "Couldn't find attachment with filename 'image_ko.png'" + expect(json_response['message']).to eq "Couldn't find attachment with filename 'image_ko.png'" end - it "returns JSON information about the attachment" do + it 'returns JSON information about the attachment' do retrieved_attachment = JSON.parse(response.body) expect(retrieved_attachment.keys).to match_array(%w[filename link]) - expect(retrieved_attachment["filename"]).to eq "image.png" - expect(retrieved_attachment["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image.png" + expect(retrieved_attachment['filename']).to eq 'image.png' + expect(retrieved_attachment['link']).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/image.png" end end - describe "POST /api/nodes/:node_id/attachments" do + describe 'POST /api/nodes/:node_id/attachments' do let(:post_attachment) { file = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) params = { files: [file] } @@ -119,13 +119,13 @@ post url , params: params, env: @env } - it "returns 201 when file saved" do + it 'returns 201 when file saved' do post_attachment expect(response.status).to eq 201 expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'rails.png'))).to be true end - it "returns 422 when no file saved" do + it 'returns 422 when no file saved' do url = "/api/nodes/#{node.id}/attachments" post url , params: {}, env: @env @@ -133,11 +133,11 @@ expect(File.exist?(Attachment.pwd.join(node.id.to_s))).to be false end - it "auto-renames the upload if an attachment with the same name already exists" do + it 'auto-renames the upload if an attachment with the same name already exists' do node_attachments = Attachment.pwd.join(node.id.to_s) - FileUtils.mkdir_p( node_attachments ) + FileUtils.mkdir_p(node_attachments) - create(:attachment, filename: "rails.png", node: node) + create(:attachment, filename: 'rails.png', node: node) expect(Dir["#{node_attachments}/*"].count).to eq(1) post_attachment @@ -145,7 +145,7 @@ expect(Dir["#{node_attachments}/*"].count).to eq(2) end - it "returns JSON information about the attachments" do + it 'returns JSON information about the attachments' do file1 = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) file2 = Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/files/rails.png')) params = { files: [file1, file2] } @@ -155,96 +155,96 @@ retrieved_attachments = JSON.parse(response.body) - attachment_0 = retrieved_attachments.detect { |n| n["filename"] == "rails.png" } - attachment_1 = retrieved_attachments.detect { |n| n["filename"] == "rails_copy-01.png" } + attachment_0 = retrieved_attachments.detect { |n| n['filename'] == 'rails.png' } + attachment_1 = retrieved_attachments.detect { |n| n['filename'] == 'rails_copy-01.png' } expect(attachment_0.keys).to match_array %w[filename link] - expect(attachment_0["filename"]).to eq "rails.png" - expect(attachment_0["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails.png" + expect(attachment_0['filename']).to eq 'rails.png' + expect(attachment_0['link']).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails.png" expect(attachment_1.keys).to match_array %w[filename link] - expect(attachment_1["filename"]).to eq "rails_copy-01.png" - expect(attachment_1["link"]).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails_copy-01.png" + expect(attachment_1['filename']).to eq 'rails_copy-01.png' + expect(attachment_1['link']).to eq "/projects/#{current_project.id}/nodes/#{node.id}/attachments/rails_copy-01.png" end end - describe "PUT /api/nodes/:node_id/attachments/:filename" do + describe 'PUT /api/nodes/:node_id/attachments/:filename' do before do - create(:attachment, filename: "image.png", node: node) + create(:attachment, filename: 'image.png', node: node) end let(:url) { "/api/nodes/#{node.id}/attachments/image.png" } let(:put_attachment) { put url, params: params.to_json, env: @env } - context "when content_type header = application/json" do - include_context "content_type: application/json" + context 'when content_type header = application/json' do + include_context 'content_type: application/json' - context "with params for a valid attachment" do - let(:params) { { attachment: { filename: "image_renamed.png" } } } + context 'with params for a valid attachment' do + let(:params) { { attachment: { filename: 'image_renamed.png' } } } - it "responds with HTTP code 200 if attachment exists" do + it 'responds with HTTP code 200 if attachment exists' do put_attachment expect(response.status).to eq 200 end - it "responds with HTTP code 404 if attachemnt doesn't exist" do + it 'responds with HTTP code 404 if attachemnt doesn\'t exist' do bad_url = "/api/nodes/#{node.id}/attachments/image_ko.png" put bad_url, params: params, env: @env expect(response.status).to eq(400) end - it "updates the attachment" do + it 'updates the attachment' do put_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be false expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image_renamed.png'))).to be true end - it "returns the attributes of the updated attachment as JSON" do + it 'returns the attributes of the updated attachment as JSON' do put_attachment retrieved_attachment = JSON.parse(response.body) - expect(retrieved_attachment["filename"]).to eq "image_renamed.png" + expect(retrieved_attachment['filename']).to eq 'image_renamed.png' end - it "responds with HTTP code 422 if attachment already exists" do - create(:attachment, filename: "image_renamed.png", node: node) + it 'responds with HTTP code 422 if attachment already exists' do + create(:attachment, filename: 'image_renamed.png', node: node) put_attachment expect(response.status).to eq(422) retrieved_attachment = JSON.parse(response.body) - expect(retrieved_attachment["filename"]).to eq "image.png" + expect(retrieved_attachment['filename']).to eq 'image.png' end end - context "with params for an invalid attachment" do - let(:params) { { attachment: { filename: "a"*65536 } } } # too long + context 'with params for an invalid attachment' do + let(:params) { { attachment: { filename: 'a' * 65536 } } } # too long - it "responds with HTTP code 422" do + it 'responds with HTTP code 422' do put_attachment expect(response.status).to eq 422 end - it "doesn't update the attachment" do + it 'doesn\'t update the attachment' do put_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image_renamed.png'))).to be false end end - context "when no :attachment param is sent" do + context 'when no :attachment param is sent' do let(:params) { {} } - it "doesn't update the attachment" do + it 'doesn\'t update the attachment' do put_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true end - it "responds with HTTP code 422" do + it 'responds with HTTP code 422' do put_attachment expect(response.status).to eq 422 end end - context "when invalid JSON is sent" do - it "responds with HTTP code 400" do + context 'when invalid JSON is sent' do + it 'responds with HTTP code 400' do json_payload = '{"attachment":{"filename":"A malformed label", , }}' put url, params: json_payload, env: @env expect(response.status).to eq(400) @@ -252,10 +252,10 @@ end end - context "when JSON is not sent" do - let(:params) { { attachment: { filename: "image_renamed.jpg" } } } + context 'when JSON is not sent' do + let(:params) { { attachment: { filename: 'image_renamed.jpg' } } } - it "responds with HTTP code 415" do + it 'responds with HTTP code 415' do put url, params: params, env: @env expect(File.exist?(Attachment.pwd.join(node.id.to_s, 'image.png'))).to be true expect(response.status).to eq 415 @@ -263,30 +263,30 @@ end end - describe "DELETE /api/nodes/:node_id/attachments/:filename" do - let(:attachment) { "image.png" } + describe 'DELETE /api/nodes/:node_id/attachments/:filename' do + let(:attachment) { 'image.png' } let(:delete_attachment) do create(:attachment, filename: attachment, node: node) delete "/api/nodes/#{node.id}/attachments/#{attachment}", env: @env end - it "deletes the attachment" do + it 'deletes the attachment' do delete_attachment expect(File.exist?(Attachment.pwd.join(node.id.to_s, attachment))).to\ - be false + be false end - it "responds with error code 200" do + it 'responds with error code 200' do delete_attachment expect(response.status).to eq(200) end - it "returns JSON with a success message" do + it 'returns JSON with a success message' do delete_attachment parsed_response = JSON.parse(response.body) - expect(parsed_response["message"]).to eq\ - "Resource deleted successfully" + expect(parsed_response['message']).to eq\ + 'Resource deleted successfully' end end end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb index 464a1e3bc..512331f7b 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/boards_spec.rb @@ -41,7 +41,7 @@ let(:params) { '' } it 'retrieves all the boards' do - retrieved_board_names = @retrieved_boards.map{ |p| p['name'] } + retrieved_board_names = @retrieved_boards.map { |p| p['name'] } expect(@retrieved_boards.count).to eq(@boards.count) expect(retrieved_board_names).to match_array(@board_names) @@ -83,7 +83,7 @@ end it 'creates a new board' do - expect{valid_post}.to change{Board.count}.by(1) + expect { valid_post }.to change { Board.count }.by(1) expect(response.status).to eq(201) retrieved_board = JSON.parse(response.body) @@ -101,7 +101,7 @@ include_examples 'creates an Activity', :create, Board it 'throws 415 unless JSON is sent' do - params = { board: { } } + params = { board: {} } post '/api/boards', params: params, env: @env expect(response.status).to eq(415) end @@ -113,7 +113,7 @@ end it 'throws 422 if no :board param is sent' do - params = { } + params = {} post '/api/boards', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end @@ -160,7 +160,7 @@ end it 'throws 422 if no :board param is sent' do - params = { } + params = {} put "/api/boards/#{ board.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb index f95971026..dd2173c46 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/lists_spec.rb @@ -118,7 +118,7 @@ end context 'with params for an invalid list' do - let(:params) { { list: { } } } # no name + let(:params) { { list: {} } } # no name it 'responds with HTTP code 422' do post_list diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb index bc69fc7ef..972636324 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb @@ -41,7 +41,7 @@ let(:params) { '' } it 'retrieves all the nodes' do - retrieved_node_labels = @retrieved_nodes.map{ |p| p['label'] } + retrieved_node_labels = @retrieved_nodes.map { |p| p['label'] } expect(@retrieved_nodes.count).to eq(@nodes.count) expect(retrieved_node_labels).to match_array(@node_labels) @@ -86,7 +86,7 @@ end it 'creates a new node' do - expect{valid_post}.to change{Node.count}.by(1) + expect { valid_post }.to change { Node.count }.by(1) expect(response.status).to eq(201) retrieved_node = JSON.parse(response.body) @@ -104,7 +104,7 @@ include_examples 'creates an Activity', :create, Node it 'throws 415 unless JSON is sent' do - params = { node: { } } + params = { node: {} } post '/api/nodes', params: params, env: @env expect(response.status).to eq(415) end @@ -116,7 +116,7 @@ end it 'throws 422 if no :node param is sent' do - params = { } + params = {} post '/api/nodes', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end @@ -170,7 +170,7 @@ end it 'throws 422 if no :node param is sent' do - params = { } + params = {} put "/api/nodes/#{ node.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') expect(response.status).to eq(422) end From 10892da10793e71b7b14fbe5555f8690fd3f08ea Mon Sep 17 00:00:00 2001 From: Caitlin Date: Mon, 28 Aug 2023 15:41:48 -0400 Subject: [PATCH 18/18] fix nodes_spec to use current_project --- engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb b/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb index 972636324..19679a9af 100644 --- a/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb +++ b/engines/dradis-api/spec/requests/dradis/ce/api/v3/nodes_spec.rb @@ -70,7 +70,7 @@ end describe 'POST /api/nodes' do - let!(:parent_node_id) { Project.new.plugin_parent_node.id } + let!(:parent_node_id) { current_project.plugin_parent_node.id } let(:valid_post) do post '/api/nodes', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json') end