diff --git a/app/controllers/hiring_staff/vacancies/documents_controller.rb b/app/controllers/hiring_staff/vacancies/documents_controller.rb
index d4ab40f2d3..8d6321ccb0 100644
--- a/app/controllers/hiring_staff/vacancies/documents_controller.rb
+++ b/app/controllers/hiring_staff/vacancies/documents_controller.rb
@@ -1,3 +1,5 @@
+require 'google/apis/drive_v3'
+
class HiringStaff::Vacancies::DocumentsController < HiringStaff::Vacancies::ApplicationController
before_action :school, :redirect_unless_vacancy_session_id, only: %i[index create]
before_action :redirect_if_no_supporting_documents, only: %i[index create]
@@ -5,10 +7,15 @@ class HiringStaff::Vacancies::DocumentsController < HiringStaff::Vacancies::Appl
def index
@documents_form = DocumentsForm.new
+ @vacancy = Vacancy.find(session[:vacancy_attributes]['id'])
end
def create
- @documents_form = DocumentsForm.new(documents_form_params)
+ @documents_form = DocumentsForm.new
+ @vacancy ||= school.vacancies.find(session_vacancy_id)
+ process_documents(documents_form_params).each do |document|
+ @vacancy.documents.create(document)
+ end
render :index
end
@@ -26,4 +33,60 @@ def redirect_if_no_supporting_documents
def redirect_to_next_step_if_save_and_continue
redirect_to application_details_school_job_path if params[:commit] == 'Save and continue'
end
+
+ def process_documents(params)
+ @file_size_limit = 10 # MB
+ documents_array = []
+ if params[:documents]&.any?
+ params[:documents].each do |document_params|
+ @errors = false
+ document_hash = upload_document(document_params)
+ unless @errors
+ documents_array << document_hash
+ end
+ end
+ end
+ documents_array
+ end
+
+ def upload_document(document_params)
+ document_upload = DocumentUpload.new(
+ upload_path: document_params.tempfile.path,
+ name: document_params.original_filename
+ )
+ if document_params.size / 1024.0 / 1024.0 > @file_size_limit
+ file_size_error(document_params.original_filename)
+ end
+ unless @errors
+ document_upload.upload
+ unless document_upload.safe_download
+ virus_error(document_params.original_filename)
+ end
+ create_document_hash(document_params, document_upload)
+ end
+ end
+
+ def file_size_error(filename)
+ @errors = true
+ @documents_form.errors.add(
+ :base, t('jobs.file_size_error_message', filename: filename, size_limit: @file_size_limit)
+ )
+ @documents_form.errors.add(:documents, t('jobs.file_input_error_message'))
+ end
+
+ def virus_error(filename)
+ @errors = true
+ @documents_form.errors.add(:base, t('jobs.file_virus_error_message', filename: filename))
+ @documents_form.errors.add(:documents, t('jobs.file_input_error_message'))
+ end
+
+ def create_document_hash(params, upload)
+ {
+ name: params.original_filename,
+ size: Integer(params.size),
+ content_type: params.content_type,
+ download_url: upload.uploaded.web_content_link,
+ google_drive_id: upload.uploaded.id
+ }
+ end
end
diff --git a/app/form_models/vacancy_form.rb b/app/form_models/vacancy_form.rb
index d788359761..3ca46eca7f 100644
--- a/app/form_models/vacancy_form.rb
+++ b/app/form_models/vacancy_form.rb
@@ -7,7 +7,9 @@ class VacancyForm
delegate :save, to: :vacancy
def initialize(params = {})
- @vacancy = Vacancy.new(params.except(:expiry_time_hh, :expiry_time_mm, :expiry_time_meridiem))
+ @vacancy = Vacancy.new(
+ params.except(:documents_attributes, :expiry_time_hh, :expiry_time_mm, :expiry_time_meridiem)
+ )
end
def school
diff --git a/app/frontend/packs/application.js b/app/frontend/packs/application.js
index 65ce305bce..257811bed8 100644
--- a/app/frontend/packs/application.js
+++ b/app/frontend/packs/application.js
@@ -16,8 +16,8 @@ import 'src/removeCommaFromNumber';
import 'src/shareUrl';
import 'src/sortJobList';
import 'src/submitFeedback';
-import 'src/vacancyShow';
import 'src/uploadDocuments';
+import 'src/vacancyShow';
import { initAll } from 'govuk-frontend';
diff --git a/app/frontend/src/uploadDocuments.js b/app/frontend/src/uploadDocuments.js
index 6c35d7c334..61761c432b 100644
--- a/app/frontend/src/uploadDocuments.js
+++ b/app/frontend/src/uploadDocuments.js
@@ -2,18 +2,68 @@ document.addEventListener('DOMContentLoaded', function() {
const inputFileUpload = document.getElementsByClassName('govuk-file-upload')[0];
const selectFileButton = document.getElementsByClassName('govuk-button--secondary')[0];
const uploadFileButton = document.getElementsByClassName('govuk-button--secondary')[1];
+ const saveContinueButton = document.getElementsByName('commit')[1];
- selectFileButton.addEventListener('click', function(e) {
- e.preventDefault();
-
- inputFileUpload.click();
- });
+ if (inputFileUpload && selectFileButton && uploadFileButton && saveContinueButton) {
+ selectFileButton.addEventListener('click', function(e) {
+ e.preventDefault();
+ inputFileUpload.click();
+ });
+
+ inputFileUpload.addEventListener('change', function(e) {
+ saveContinueButton.disabled = true;
+ injectDocumentsTable(inputFileUpload);
+ inputFileUpload.form.submit();
+ });
+
+ inputFileUpload.classList.add('display-none');
+ uploadFileButton.classList.add('display-none');
+ selectFileButton.classList.remove('display-none');
+ }
+});
- inputFileUpload.addEventListener('change', function(e) {
- inputFileUpload.form.submit();
- });
+injectDocumentsTable = function(documentsInput) {
+ const tableHTML = " \
+
\
+ \
+ \
+
\
+ \
+ \
+ \
+
\
+ "
+ const noFilesElement = document.getElementById('no-files');
+ const filesList = documentsInput.files;
- inputFileUpload.classList.add('display-none');
- uploadFileButton.classList.add('display-none');
- selectFileButton.classList.remove('display-none');
-});
+ if (filesList && filesList.length) {
+ if (noFilesElement) {
+ noFilesElement.insertAdjacentHTML("beforebegin", tableHTML);
+ noFilesElement.remove();
+ }
+ for (let i = 0; i < filesList.length; i++) {
+ const rowHTML = " \
+ \
+ " +
+ filesList[i].name +
+ " | \
+ " +
+ "Uploading " + "" +
+ " | \
+ " +
+ (filesList[i].size / 1024.0 / 1024.0).toFixed(2) + " MB" +
+ " | \
+ " +
+ "Cancel" +
+ " | \
+
\
+ "
+ const tableBody = document.getElementById('table-body');
+ tableBody.insertAdjacentHTML("beforeend", rowHTML);
+ }
+ }
+}
diff --git a/app/frontend/styles/components/upload-documents.scss b/app/frontend/styles/components/upload-documents.scss
index c69c688b18..651245ab61 100644
--- a/app/frontend/styles/components/upload-documents.scss
+++ b/app/frontend/styles/components/upload-documents.scss
@@ -1,7 +1,43 @@
-.upload-group .govuk-button {
+#file-upload {
margin-bottom: 0;
}
.hidden-input {
display: none;
}
+
+.upload-progress {
+ display: inline-block;
+ position: relative;
+ width: 18px;
+ height: 18px;
+}
+.upload-progress div {
+ box-sizing: border-box;
+ display: block;
+ position: absolute;
+ width: 18px;
+ height: 18px;
+ margin: 0px;
+ border: 3px solid green;
+ border-radius: 50%;
+ animation: spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ border-color: green transparent transparent transparent;
+}
+.upload-progress div:nth-child(1) {
+ animation-delay: -0.45s;
+}
+.upload-progress div:nth-child(2) {
+ animation-delay: -0.3s;
+}
+.upload-progress div:nth-child(3) {
+ animation-delay: -0.15s;
+}
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/app/models/document.rb b/app/models/document.rb
new file mode 100644
index 0000000000..2e26f47956
--- /dev/null
+++ b/app/models/document.rb
@@ -0,0 +1,4 @@
+class Document < ApplicationRecord
+ belongs_to :vacancy
+ validates :name, :size, :content_type, :download_url, :google_drive_id, presence: true
+end
diff --git a/app/models/vacancy.rb b/app/models/vacancy.rb
index cdc0530268..9ba2d58f58 100644
--- a/app/models/vacancy.rb
+++ b/app/models/vacancy.rb
@@ -88,8 +88,6 @@ class Vacancy < ApplicationRecord
extend FriendlyId
extend ArrayEnum
- attr_accessor :documents
-
friendly_id :slug_candidates, use: %w[slugged history]
enum status: { published: 0, draft: 1, trashed: 2 }
@@ -122,6 +120,8 @@ class Vacancy < ApplicationRecord
has_one :publish_feedback, class_name: 'VacancyPublishFeedback'
+ has_many :documents
+
delegate :name, to: :school, prefix: true, allow_nil: false
delegate :geolocation, to: :school, prefix: true, allow_nil: true
diff --git a/app/services/document_upload.rb b/app/services/document_upload.rb
new file mode 100644
index 0000000000..d35129e601
--- /dev/null
+++ b/app/services/document_upload.rb
@@ -0,0 +1,52 @@
+require 'google/apis/drive_v3'
+
+class DocumentUpload
+ class MissingUploadPath < StandardError; end
+ attr_accessor :drive_service, :upload_path, :name, :uploaded, :safe_download
+
+ def initialize(opts = {})
+ raise MissingUploadPath if opts[:upload_path].nil?
+ self.upload_path = opts[:upload_path]
+ self.name = opts[:name]
+ self.drive_service = Google::Apis::DriveV3::DriveService.new
+ end
+
+ def upload
+ upload_hiring_staff_document
+ set_public_permission_on_document
+ google_drive_virus_check
+ end
+
+ def upload_hiring_staff_document
+ self.uploaded = drive_service.create_file(
+ { alt: 'media', name: name },
+ fields: 'id, web_view_link, web_content_link, mime_type',
+ upload_source: upload_path
+ )
+ end
+
+ def set_public_permission_on_document
+ drive_service.create_permission(
+ uploaded.id,
+ Google::Apis::DriveV3::Permission.new(type: 'anyone', role: 'reader')
+ )
+ end
+
+ def google_drive_virus_check
+ download_path = "#{uploaded.id}"
+ begin
+ drive_service.get_file(
+ uploaded.id,
+ acknowledge_abuse: false,
+ download_dest: download_path
+ )
+ rescue Google::Apis::ClientError
+ drive_service.delete_file(uploaded.id)
+ self.safe_download = false
+ else
+ self.safe_download = true
+ ensure
+ File.delete(download_path) if File.exist?(download_path)
+ end
+ end
+end
diff --git a/app/views/hiring_staff/documents/edit.html.erb b/app/views/hiring_staff/documents/edit.html.erb
deleted file mode 100644
index 2bb438fcaf..0000000000
--- a/app/views/hiring_staff/documents/edit.html.erb
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
Create a job listing for Bexley Heath Academy
- Step 2 of 3
-
-
-
-
-
-
diff --git a/app/views/hiring_staff/documents/index.html.erb b/app/views/hiring_staff/documents/index.html.erb
deleted file mode 100644
index 8621147a56..0000000000
--- a/app/views/hiring_staff/documents/index.html.erb
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
Create a job listing for Bexley Heath Academy
- Step 2 of 3
-
-
-
-
-
-
diff --git a/app/views/hiring_staff/documents/new.html.erb b/app/views/hiring_staff/documents/new.html.erb
deleted file mode 100644
index bae0293e2f..0000000000
--- a/app/views/hiring_staff/documents/new.html.erb
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
Create a job listing for Bexley Heath Academy
- Step 2 of 3
-
-
-
-
-
-
-
-
-
Supporting documents
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/views/hiring_staff/vacancies/documents/_documents.html.haml b/app/views/hiring_staff/vacancies/documents/_documents.html.haml
index 801bb2303b..36010882df 100644
--- a/app/views/hiring_staff/vacancies/documents/_documents.html.haml
+++ b/app/views/hiring_staff/vacancies/documents/_documents.html.haml
@@ -1,15 +1,17 @@
%table.govuk-table
%thead.govuk-table__head
%tr.govuk-table__row
- %th.govuk-table__header File name
- %th.govuk-table__header Status
- %th.govuk-table__header File size
- %th.govuk-table__header Action
- %tbody.govuk-table__body
+ %th.govuk-table__header= t('jobs.upload_documents_table.headers.file_name')
+ %th.govuk-table__header= t('jobs.upload_documents_table.headers.status')
+ %th.govuk-table__header= t('jobs.upload_documents_table.headers.file_size')
+ %th.govuk-table__header= t('jobs.upload_documents_table.headers.action')
+ %tbody#table-body.govuk-table__body
- documents.each do |document|
%tr.govuk-table__row
- %th.govuk-table__header{ scope: 'row' } #{document.original_filename}
+ %td.govuk-table__cell{ scope: 'row' }
+ %a.govuk-link{ href: document[:download_url], target: '_blank' } #{document[:name]}
+ %td.govuk-table__cell= t('upload_documents_table.upload_status.uploaded')
+ %td.govuk-table__cell{ scope: 'row' } #{number_with_precision(document[:size] / 1024.0 / 1024.0, precision: 2)} MB
%td.govuk-table__cell
- %td.govuk-table__cell
- %td.govuk-table__cell
- %a{ href: '#' } Remove
+ %a.govuk-link.govuk-link--no-visited-state{ href: '#' }
+ = t('upload_documents_table.actions.remove')
diff --git a/app/views/hiring_staff/vacancies/documents/index.html.haml b/app/views/hiring_staff/vacancies/documents/index.html.haml
index 473b898bdc..757a1d53de 100644
--- a/app/views/hiring_staff/vacancies/documents/index.html.haml
+++ b/app/views/hiring_staff/vacancies/documents/index.html.haml
@@ -7,7 +7,7 @@
.govuk-grid-row
.govuk-grid-column-two-thirds
- = form_for @documents_form, url: documents_school_job_path(school_id: @school.id), builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f|
+ = form_for @documents_form, url: documents_school_job_path, builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f|
= f.govuk_error_summary 'Please correct the following errors'
%h2.govuk-heading-m
@@ -23,9 +23,9 @@
= f.govuk_submit t('jobs.upload_file'), secondary: true
- - if @documents_form.vacancy.documents&.any?
- = render 'hiring_staff/vacancies/documents/documents', documents: @documents_form.vacancy.documents
+ - if @vacancy&.documents&.any?
+ = render 'hiring_staff/vacancies/documents/documents', documents: @vacancy.documents
- else
- %p= t('jobs.no_files_message')
+ %p#no-files= t('jobs.no_files_message')
= f.govuk_submit t('buttons.save_and_continue')
diff --git a/config/initializers/google_api.rb b/config/initializers/google_api.rb
index a02efc6bd3..88e3a3cc28 100644
--- a/config/initializers/google_api.rb
+++ b/config/initializers/google_api.rb
@@ -8,7 +8,8 @@
end
scope = ['https://www.googleapis.com/auth/indexing',
- 'https://www.googleapis.com/auth/analytics']
+ 'https://www.googleapis.com/auth/analytics',
+ 'https://www.googleapis.com/auth/drive']
key = StringIO.new(GOOGLE_API_JSON_KEY)
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: key,
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 92aecb4df5..2edd44f552 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -168,6 +168,21 @@ en:
upload_file: 'Upload files'
select_file: 'Select a file'
no_files_message: 'No files chosen'
+ file_input_error_message: 'The selected file(s) could not be uploaded'
+ file_size_error_message: '%{filename} must be smaller than %{size_limit} MB'
+ file_virus_error_message: '%{filename} contains a virus'
+ upload_documents_table:
+ headers:
+ file_name: 'File name'
+ status: 'Status'
+ file_size: 'File size'
+ action: 'Action'
+ upload_status:
+ uploading: 'Uploading...'
+ uploaded: 'Uploaded'
+ actions:
+ remove: 'Remove'
+ cancel: 'Cancel'
application_details: 'Application details'
no_contact_email: 'No job contact email given'
contact_email: 'Job contact email'
diff --git a/db/migrate/20200211085235_create_documents.rb b/db/migrate/20200211085235_create_documents.rb
new file mode 100644
index 0000000000..9eb41231ca
--- /dev/null
+++ b/db/migrate/20200211085235_create_documents.rb
@@ -0,0 +1,13 @@
+class CreateDocuments < ActiveRecord::Migration[5.2]
+ def change
+ create_table :documents, id: :uuid do |t|
+ t.string :name, null: false
+ t.integer :size, null: false
+ t.string :content_type, null: false
+ t.string :download_url, null: false
+ t.string :google_drive_id, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20200211085721_add_vacancy_to_documents.rb b/db/migrate/20200211085721_add_vacancy_to_documents.rb
new file mode 100644
index 0000000000..0093bcb45f
--- /dev/null
+++ b/db/migrate/20200211085721_add_vacancy_to_documents.rb
@@ -0,0 +1,5 @@
+class AddVacancyToDocuments < ActiveRecord::Migration[5.2]
+ def change
+ add_reference :documents, :vacancy, foreign_key: true, type: :uuid
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 74c2b2e8be..278017486c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -57,6 +57,18 @@
t.index ["code"], name: "index_detailed_school_types_on_code", unique: true
end
+ create_table "documents", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+ t.string "name", null: false
+ t.integer "size", null: false
+ t.string "content_type", null: false
+ t.string "download_url", null: false
+ t.string "google_drive_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.uuid "vacancy_id"
+ t.index ["vacancy_id"], name: "index_documents_on_vacancy_id"
+ end
+
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.uuid "sluggable_id", null: false
@@ -245,6 +257,7 @@
t.index ["vacancy_id"], name: "index_vacancy_publish_feedbacks_on_vacancy_id", unique: true
end
+ add_foreign_key "documents", "vacancies"
add_foreign_key "schools", "detailed_school_types"
add_foreign_key "vacancies", "users", column: "publisher_user_id"
end
diff --git a/spec/controllers/hiring_staff/vacancies/documents_controller_spec.rb b/spec/controllers/hiring_staff/vacancies/documents_controller_spec.rb
deleted file mode 100644
index 2d96f6b60f..0000000000
--- a/spec/controllers/hiring_staff/vacancies/documents_controller_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe HiringStaff::Vacancies::DocumentsController, type: :controller do
- it 'redirects to the page after a successful upload' do
- # This may change as upload functionality is being defined.
- post :create, params: { upload: fixture_file_upload(Rails.root.join('spec/fixtures/files/blank_job_spec.pdf')) }
- expect(response.status).to eq(302)
- end
-end
diff --git a/spec/factories/documents.rb b/spec/factories/documents.rb
new file mode 100644
index 0000000000..bcb01e1910
--- /dev/null
+++ b/spec/factories/documents.rb
@@ -0,0 +1,10 @@
+FactoryBot.define do
+ factory :document do
+ name { 'Test.png' }
+ size { 10000 }
+ content_type { 'image/png' }
+ download_url { 'test/test.png' }
+ google_drive_id { 'testid' }
+ vacancy
+ end
+end
diff --git a/spec/factories/vacancies.rb b/spec/factories/vacancies.rb
index 60e4171cf3..fd229fb484 100644
--- a/spec/factories/vacancies.rb
+++ b/spec/factories/vacancies.rb
@@ -6,6 +6,10 @@
association :leadership
association :school
+ after :create do |vacancy|
+ create_list :document, 3, vacancy: vacancy
+ end
+
job_title { Faker::Lorem.sentence[1...30].strip }
job_description { Faker::Lorem.paragraph(sentence_count: 4) }
education { Faker::Lorem.paragraph(sentence_count: 4) }
diff --git a/spec/features/hiring_staff_can_publish_a_vacancy_spec.rb b/spec/features/hiring_staff_can_publish_a_vacancy_spec.rb
index 47752b4137..b5d919179e 100644
--- a/spec/features/hiring_staff_can_publish_a_vacancy_spec.rb
+++ b/spec/features/hiring_staff_can_publish_a_vacancy_spec.rb
@@ -196,6 +196,90 @@
end
end
+ context '#documents' do
+ let(:feature_enabled?) { true }
+
+ before do
+ visit new_school_job_path
+ fill_in_job_specification_form_fields(vacancy)
+ click_on 'Save and continue'
+ end
+
+ scenario 'redirects to step 2, supporting_documents, when that step has not been completed' do
+ visit documents_school_job_path
+ expect(page.current_path).to eq(supporting_documents_school_job_path)
+ end
+
+ scenario 'hiring staff can select a file for upload' do
+ fill_in_supporting_documents_form_fields(vacancy)
+ click_on 'Save and continue'
+
+ page.attach_file('documents-form-documents-field', Rails.root.join('spec/fixtures/files/blank_job_spec.pdf'))
+ expect(page.find('#documents-form-documents-field').value).to_not be nil
+ end
+
+ scenario 'redirects to step 3, application_details, when submitted' do
+ fill_in_supporting_documents_form_fields(vacancy)
+ click_on 'Save and continue'
+
+ click_on 'Save and continue'
+ expect(page.current_path).to eq(application_details_school_job_path)
+ end
+
+ context 'when uploading files' do
+ let(:document_upload) { double('document_upload') }
+
+ before do
+ allow(DocumentUpload).to receive(:new).and_return(document_upload)
+ allow(document_upload).to receive(:upload)
+ allow(document_upload).to receive_message_chain(:uploaded, :web_content_link).and_return('test_url')
+ allow(document_upload).to receive_message_chain(:uploaded, :id).and_return('test_id')
+ allow(document_upload).to receive(:safe_download).and_return(true)
+ end
+
+ scenario 'displays uploaded file in a table' do
+ fill_in_supporting_documents_form_fields(vacancy)
+ click_on 'Save and continue'
+
+ upload_document(
+ 'new_documents_form',
+ 'documents-form-documents-field',
+ 'spec/fixtures/files/blank_job_spec.pdf'
+ )
+
+ expect(page).to have_content('blank_job_spec.pdf')
+ end
+
+ scenario 'displays error message when large file is uploaded' do
+ fill_in_supporting_documents_form_fields(vacancy)
+ click_on 'Save and continue'
+
+ upload_document(
+ 'new_documents_form',
+ 'documents-form-documents-field',
+ 'spec/fixtures/files/large_blank_job_spec.pdf'
+ )
+
+ expect(page).to have_content('large_blank_job_spec.pdf must be smaller than')
+ end
+
+ scenario 'displays error message when virus file is uploaded' do
+ fill_in_supporting_documents_form_fields(vacancy)
+ click_on 'Save and continue'
+
+ allow(document_upload).to receive(:safe_download).and_return(false)
+
+ upload_document(
+ 'new_documents_form',
+ 'documents-form-documents-field',
+ 'spec/fixtures/files/blank_job_spec.pdf'
+ )
+
+ expect(page).to have_content('blank_job_spec.pdf contains a virus')
+ end
+ end
+ end
+
context '#application_details' do
scenario 'is invalid unless all mandatory fields are submitted' do
visit new_school_job_path
diff --git a/spec/features/hiring_staff_can_upload_documents_spec.rb b/spec/features/hiring_staff_can_upload_documents_spec.rb
deleted file mode 100644
index 1817d2cc0d..0000000000
--- a/spec/features/hiring_staff_can_upload_documents_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'rails_helper'
-
-RSpec.feature 'Hiring staff can upload documents to a vacancy' do
- let(:feature_enabled?) { true }
- let(:session_id) { SecureRandom.uuid }
- let(:school) { create(:school) }
- let!(:pay_scales) { create_list(:pay_scale, 3) }
- let!(:subjects) { create_list(:subject, 3) }
- let!(:leaderships) { create_list(:leadership, 3) }
- let(:vacancy) do
- VacancyPresenter.new(build(:vacancy, :complete,
- school: school,
- min_pay_scale: pay_scales.sample,
- max_pay_scale: pay_scales.sample,
- subject: subjects[0],
- first_supporting_subject: subjects[1],
- second_supporting_subject: subjects[2],
- leadership: leaderships.sample,
- working_patterns: ['full_time', 'part_time'],
- publish_on: Time.zone.today))
- end
-
- before do
- allow(UploadDocumentsFeature).to receive(:enabled?).and_return(feature_enabled?)
- stub_hiring_staff_auth(urn: school.urn)
-
- visit new_school_job_path
- fill_in_job_specification_form_fields(vacancy)
- click_on 'Save and continue'
- end
-
- context 'when the URL is accessed directly' do
- scenario 'redirects the user to the supporting documents option select page' do
- visit documents_school_job_path
- expect(page.current_path).to eq(supporting_documents_school_job_path)
- end
- end
-
- context "when the user selects 'yes' in the previous step" do
- before do
- visit supporting_documents_school_job_path
- fill_in_supporting_documents_form_fields(vacancy)
- click_on 'Save and continue'
- end
-
- scenario 'users lands on upload documents page' do
- expect(page.current_path).to eq(documents_school_job_path)
- expect(page).to have_content('Upload files')
- end
-
- scenario 'hiring staff can select a file for upload' do
- page.attach_file('documents-form-documents-field', Rails.root.join('spec/fixtures/files/blank_job_spec.pdf'))
- expect(page.find('#documents-form-documents-field').value).to_not be nil
- end
-
- scenario 'hiring staff can continue to next page' do
- click_on 'Save and continue'
- expect(page.current_path).to eq(application_details_school_job_path)
- end
- end
-end
diff --git a/spec/fixtures/files/large_blank_job_spec.pdf b/spec/fixtures/files/large_blank_job_spec.pdf
new file mode 100644
index 0000000000..15ae5f2c4c
Binary files /dev/null and b/spec/fixtures/files/large_blank_job_spec.pdf differ
diff --git a/spec/models/document_spec.rb b/spec/models/document_spec.rb
new file mode 100644
index 0000000000..85e50be665
--- /dev/null
+++ b/spec/models/document_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+RSpec.describe Document, type: :model do
+ it { should belong_to(:vacancy) }
+
+ describe 'validations' do
+ context 'a new record' do
+ it { should validate_presence_of(:name) }
+ it { should validate_presence_of(:size) }
+ it { should validate_presence_of(:content_type) }
+ it { should validate_presence_of(:download_url) }
+ it { should validate_presence_of(:google_drive_id) }
+ end
+ end
+end
diff --git a/spec/models/vacancy_spec.rb b/spec/models/vacancy_spec.rb
index 0d02494a44..40b829acf9 100644
--- a/spec/models/vacancy_spec.rb
+++ b/spec/models/vacancy_spec.rb
@@ -3,6 +3,7 @@
subject { Vacancy.new(school: build(:school)) }
it { should belong_to(:school) }
it { should belong_to(:publisher_user) }
+ it { should have_many(:documents) }
describe '.public_search' do
context 'when there were no results' do
@@ -499,6 +500,20 @@
end
end
+ describe 'when supporting documents are provided' do
+ it 'should return the document name' do
+ document = create(
+ :document, name: 'Test.png',
+ size: 1000,
+ content_type: 'image/png',
+ download_url: 'test/test.png',
+ google_drive_id: 'testid'
+ )
+ vacancy = create(:vacancy, documents: [document])
+ expect(vacancy.documents.first.name).to eq('Test.png')
+ end
+ end
+
describe 'delegate school_name' do
it 'should return the school name for the vacancy' do
school = create(:school, name: 'St James School')
diff --git a/spec/services/document_upload_spec.rb b/spec/services/document_upload_spec.rb
new file mode 100644
index 0000000000..586f0ae19f
--- /dev/null
+++ b/spec/services/document_upload_spec.rb
@@ -0,0 +1,102 @@
+require 'rails_helper'
+
+RSpec.describe DocumentUpload do
+ subject { described_class.new(upload_path: upload_path, name: name) }
+
+ let(:drive_service_stub) { double(Google::Apis::DriveV3::DriveService) }
+ let(:create_file) { double('create_file') }
+ let(:upload_path) { 'test.pdf' }
+ let(:name) { 'Test file' }
+
+ it 'raises MissingUploadPath error when called without an argument' do
+ expect { described_class.new }.to raise_error(described_class::MissingUploadPath)
+ end
+
+ context 'upload_hiring_staff_document' do
+ it 'calls create_file on drive_service' do
+ expect(subject.drive_service).to receive(:create_file).with(
+ { alt: 'media', name: name },
+ fields: 'id, web_view_link, web_content_link, mime_type',
+ upload_source: anything()
+ )
+ subject.upload_hiring_staff_document
+ end
+
+ it 'explicity expects the temporary file path' do
+ expect(subject.drive_service).to receive(:create_file).with(
+ anything(),
+ hash_including(upload_source: upload_path)
+ )
+ subject.upload_hiring_staff_document
+ end
+ end
+
+ context 'set_public_permission_on_document' do
+ before do
+ allow(subject).to receive(:uploaded).and_return(create_file)
+ end
+
+ it 'calls create_permission on drive_service' do
+ allow(create_file).to receive(:id)
+ expect(subject.drive_service).to receive(:create_permission).with(
+ anything(),
+ anything()
+ )
+ subject.set_public_permission_on_document
+ end
+
+ it 'calls create_permission with id returned by create_file call' do
+ expect(create_file).to receive(:id)
+ allow(subject.drive_service).to receive(:create_permission)
+ subject.set_public_permission_on_document
+ end
+
+ it 'calls create_permission with Google::Apis::DriveV3::Permission' do
+ allow(create_file).to receive(:id)
+ allow(subject.drive_service).to receive(:create_permission)
+ expect(Google::Apis::DriveV3::Permission).to receive(:new).with(
+ type: 'anyone', role: 'reader'
+ )
+ subject.set_public_permission_on_document
+ end
+ end
+
+ context 'google_drive_virus_check' do
+ let(:drive_error_stub) { double(Google::Apis::ClientError) }
+
+ before do
+ allow(subject).to receive(:uploaded).and_return(create_file)
+ end
+
+ it 'calls get_file on drive_service' do
+ allow(create_file).to receive(:id)
+ expect(subject.drive_service).to receive(:get_file).with(
+ anything(),
+ anything()
+ )
+ subject.google_drive_virus_check
+ end
+
+ it 'calls get_file with id returned by create_file call' do
+ expect(create_file).to receive(:id).twice
+ allow(subject.drive_service).to receive(:get_file)
+ subject.google_drive_virus_check
+ end
+
+ it 'calls delete_file on drive_service if Google::Apis::ClientError raised' do
+ allow(create_file).to receive(:id)
+ # This error needs to initialised with an argument in order to be raised.
+ allow(subject.drive_service).to receive(:get_file).and_raise(Google::Apis::ClientError.new(true))
+ expect(subject.drive_service).to receive(:delete_file)
+ subject.google_drive_virus_check
+ end
+
+ it 'calls delete_file with id returned by create_file call on drive_service if Google::Apis::ClientError raised' do
+ expect(create_file).to receive(:id).exactly(3).times
+ # This error needs to initialised with an argument in order to be raised.
+ allow(subject.drive_service).to receive(:get_file).and_raise(Google::Apis::ClientError.new(true))
+ allow(subject.drive_service).to receive(:delete_file)
+ subject.google_drive_virus_check
+ end
+ end
+end
diff --git a/spec/support/vacancy_helpers.rb b/spec/support/vacancy_helpers.rb
index 5f0ae0723a..2980c70fea 100644
--- a/spec/support/vacancy_helpers.rb
+++ b/spec/support/vacancy_helpers.rb
@@ -37,6 +37,13 @@ def fill_in_supporting_documents_form_fields(vacancy)
choose 'Yes'
end
+ def upload_document(form_id, input_name, filepath)
+ page.attach_file(input_name, Rails.root.join(filepath))
+ # Submit form on file upload without requiring Javascript driver
+ form = page.find("##{form_id}")
+ Capybara::RackTest::Form.new(page.driver, form.native).submit(form)
+ end
+
def fill_in_application_details_form_fields(vacancy)
fill_in 'application_details_form[contact_email]', with: vacancy.contact_email
fill_in 'application_details_form[application_link]', with: vacancy.application_link
diff --git a/spec/views/hiring_staff/documents/edit.html.erb_spec.rb b/spec/views/hiring_staff/documents/edit.html.erb_spec.rb
deleted file mode 100644
index 600bedf064..0000000000
--- a/spec/views/hiring_staff/documents/edit.html.erb_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'hiring_staff/documents/edit' do
- before do
- render
- end
-
- it 'renders something' do
- expect(rendered).not_to be_blank
- end
-
- it 'shows that we are on step two of the process' do
- expect(rendered).to match(/step 2 of 3/i)
- end
-
- it 'shows "Upload a file"' do
- expect(rendered).to match('Upload a file')
- end
-end
diff --git a/spec/views/hiring_staff/documents/index.html.erb_spec.rb b/spec/views/hiring_staff/documents/index.html.erb_spec.rb
deleted file mode 100644
index dbf228b950..0000000000
--- a/spec/views/hiring_staff/documents/index.html.erb_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'hiring_staff/documents/index' do
- before do
- render
- end
-
- it 'renders something' do
- expect(rendered).not_to be_blank
- end
-
- it 'shows that we are on step two of the process' do
- expect(rendered).to match(/step 2 of 3/i)
- end
-
- it 'includes the govuk table' do
- expect(rendered).to have_css('table.govuk-table')
- end
-end
diff --git a/spec/views/hiring_staff/documents/new.html.erb_spec.rb b/spec/views/hiring_staff/documents/new.html.erb_spec.rb
deleted file mode 100644
index 89b6d3b736..0000000000
--- a/spec/views/hiring_staff/documents/new.html.erb_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'hiring_staff/documents/new' do
- before do
- render
- end
-
- it 'renders something' do
- expect(rendered).not_to be_blank
- end
-
- it 'shows that we are on step two of the process' do
- expect(rendered).to match(/step 2 of 3/i)
- end
-
- it 'shows the yes/no control' do
- expect(rendered).to match('Yes')
- expect(rendered).to match('No')
- end
-end