diff --git a/Gemfile b/Gemfile index e644e0e6..ba751188 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ gem "kamal", require: false gem "thruster", require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -# gem "image_processing", "~> 1.2" +gem "image_processing", "~> 1.2" group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/Gemfile.lock b/Gemfile.lock index 7be43f71..4dcc2420 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,6 +142,9 @@ GEM zeitwerk i18n (1.14.7) concurrent-ruby (~> 1.0) + image_processing (1.13.0) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) importmap-rails (2.1.0) actionpack (>= 6.0.0) activesupport (>= 6.0.0) @@ -187,6 +190,7 @@ GEM net-smtp marcel (1.0.4) matrix (0.4.2) + mini_magick (4.13.2) mini_mime (1.1.5) minitest (5.25.4) msgpack (1.7.5) @@ -340,6 +344,9 @@ GEM rubocop-performance rubocop-rails ruby-progressbar (1.13.0) + ruby-vips (2.2.2) + ffi (~> 1.12) + logger rubyzip (2.4.1) securerandom (0.4.1) selenium-webdriver (4.28.0) @@ -430,6 +437,7 @@ DEPENDENCIES factory_bot_rails faker hotwire-spark + image_processing (~> 1.2) importmap-rails jbuilder kamal diff --git a/app/controllers/training_resources_controller.rb b/app/controllers/training_resources_controller.rb new file mode 100644 index 00000000..3d708d41 --- /dev/null +++ b/app/controllers/training_resources_controller.rb @@ -0,0 +1,70 @@ +class TrainingResourcesController < ApplicationController + before_action :set_training_resource, only: %i[ show edit update destroy ] + + # GET /training_resources or /training_resources.json + def index + @training_resources = TrainingResource.all + end + + # GET /training_resources/1 or /training_resources/1.json + def show + end + + # GET /training_resources/new + def new + @training_resource = TrainingResource.new + end + + # GET /training_resources/1/edit + def edit + end + + # POST /training_resources or /training_resources.json + def create + @training_resource = TrainingResource.new(training_resource_params) + + respond_to do |format| + if @training_resource.save + format.html { redirect_to @training_resource, notice: "Training resource was successfully created." } + format.json { render :show, status: :created, location: @training_resource } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @training_resource.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /training_resources/1 or /training_resources/1.json + def update + respond_to do |format| + if @training_resource.update(training_resource_params) + format.html { redirect_to @training_resource, notice: "Training resource was successfully updated." } + format.json { render :show, status: :ok, location: @training_resource } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @training_resource.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /training_resources/1 or /training_resources/1.json + def destroy + @training_resource.destroy! + + respond_to do |format| + format.html { redirect_to training_resources_path, status: :see_other, notice: "Training resource was successfully destroyed." } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_training_resource + @training_resource = TrainingResource.find(params.expect(:id)) + end + + # Only allow a list of trusted parameters through. + def training_resource_params + params.expect(training_resource: [ :state, :document, :topic_id, :file_name_override ]) + end +end diff --git a/app/helpers/training_resources_helper.rb b/app/helpers/training_resources_helper.rb new file mode 100644 index 00000000..918caa3a --- /dev/null +++ b/app/helpers/training_resources_helper.rb @@ -0,0 +1,2 @@ +module TrainingResourcesHelper +end diff --git a/app/models/topic.rb b/app/models/topic.rb index 7cb9c545..486b72f7 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -3,6 +3,7 @@ class Topic < ApplicationRecord belongs_to :language belongs_to :provider + has_many :training_resources validates :title, :language_id, :provider_id, presence: true diff --git a/app/models/training_resource.rb b/app/models/training_resource.rb new file mode 100644 index 00000000..d105ddb4 --- /dev/null +++ b/app/models/training_resource.rb @@ -0,0 +1,10 @@ +class TrainingResource < ApplicationRecord + belongs_to :topic + has_one :language, through: :topic + has_one_attached :document + + validates :state, presence: true + + validates_with DocumentValidator + validates_with ResourceLanguageValidator +end diff --git a/app/validators/document_validator.rb b/app/validators/document_validator.rb new file mode 100644 index 00000000..edb13ed3 --- /dev/null +++ b/app/validators/document_validator.rb @@ -0,0 +1,10 @@ +class DocumentValidator < ActiveModel::Validator + VALID_TYPES = %w[image/jpeg image/jpg image/png image/svg image/webp image/avif image/gif videos/mp4 ].freeze + def validate(record) + return unless record.document.attached? + + unless record.document.blob.content_type.in?(VALID_TYPES) + record.errors.add(:document, "Document has an invalid content type (authorized content type is JPG,PNG)") + end + end +end diff --git a/app/validators/resource_language_validator.rb b/app/validators/resource_language_validator.rb new file mode 100644 index 00000000..b040fb74 --- /dev/null +++ b/app/validators/resource_language_validator.rb @@ -0,0 +1,14 @@ +class ResourceLanguageValidator < ActiveModel::Validator + def validate(record) + return if record.file_name_override.blank? + + if TrainingResource.joins(:topic) + .where( + training_resources: { file_name_override: record.file_name_override }, + topics: { language_id: record.topic.language_id }, + ).where.not(id: record.id) + .any? + record.errors.add(:file_name_override, "This filename is already in use") + end + end +end diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb index f7761865..2ac8dbcc 100644 --- a/app/views/layouts/_sidebar.html.erb +++ b/app/views/layouts/_sidebar.html.erb @@ -81,6 +81,13 @@ Users <% end %> + + <% end %> diff --git a/app/views/training_resources/_form.html.erb b/app/views/training_resources/_form.html.erb new file mode 100644 index 00000000..03bf2595 --- /dev/null +++ b/app/views/training_resources/_form.html.erb @@ -0,0 +1,48 @@ +<%= form_with(model: training_resource) do |form| %> + <% if training_resource.errors.any? %> +
+

<%= pluralize(training_resource.errors.count, "error") %> prohibited this training_resource from being saved:

+ + +
+ <% end %> + +
+
+ <%= form.label :topic, style: "display: block" %> + <%= form.collection_select :topic_id, Topic.order(:title), :id, :title, { prompt: "Select topic" }, class: "form-select" %> +
+
+ +
+
+ <%= form.label :state, style: "display: block" %> + <%= form.number_field :state, class: "form-control", autofocus: true %> +
+
+ +
+
+ <%= form.label :file_name_override, style: "display: block" %> + <%= form.text_field :file_name_override, class: "form-control" %> +
+
+ +
+
+ <%= form.label :document, style: "display: block" %> + <%= form.file_field :document, class: "form-control" %> +
+
+ +
+
+ <%= form.submit "Create Training Resource", class: "btn btn-primary me-1 mb-1" %> + <%= link_to "Cancel", training_resources_path, class: "btn btn-light-secondary me-1 mb-1" %> +
+
+<% end %> diff --git a/app/views/training_resources/_training_resource.html.erb b/app/views/training_resources/_training_resource.html.erb new file mode 100644 index 00000000..c10a5e88 --- /dev/null +++ b/app/views/training_resources/_training_resource.html.erb @@ -0,0 +1,12 @@ +
+

+ State: + <%= training_resource.state %> +

+ +

+ Document: + <%= link_to training_resource.document.filename, training_resource.document if training_resource.document.attached? %> +

+ +
diff --git a/app/views/training_resources/_training_resource.json.jbuilder b/app/views/training_resources/_training_resource.json.jbuilder new file mode 100644 index 00000000..2d37bb25 --- /dev/null +++ b/app/views/training_resources/_training_resource.json.jbuilder @@ -0,0 +1,3 @@ +json.extract! training_resource, :id, :state, :document, :created_at, :updated_at +json.url training_resource_url(training_resource, format: :json) +json.document url_for(training_resource.document) diff --git a/app/views/training_resources/edit.html.erb b/app/views/training_resources/edit.html.erb new file mode 100644 index 00000000..18dc9fa9 --- /dev/null +++ b/app/views/training_resources/edit.html.erb @@ -0,0 +1,12 @@ +<% content_for :title, "Editing training resource" %> + +

Editing training resource

+ +<%= render "form", training_resource: @training_resource %> + +
+ +
+ <%= link_to "Show this training resource", @training_resource %> | + <%= link_to "Back to training resources", training_resources_path %> +
diff --git a/app/views/training_resources/index.html.erb b/app/views/training_resources/index.html.erb new file mode 100644 index 00000000..bb660292 --- /dev/null +++ b/app/views/training_resources/index.html.erb @@ -0,0 +1,67 @@ +<% content_for :title, "Training Resources" %> + +
+
+
+
+
+

Training Resources

+ <%= link_to new_training_resource_path, class: "btn btn-primary" do %> + Add Training Resource + <% end %> +
+
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est + laborum. +

+ +
+ + + + + + + + + + + + <% @training_resources.each do |training_resource| %> + + + + + + + + <% end %> + +
TopicStateFilename OverrideDocumentActions
<%= training_resource.topic.title %><%= training_resource.state %><%= training_resource.file_name_override %><%= training_resource.document.key %> + <%= link_to training_resource, class: "btn btn-primary btn-sm" do %> + View + <% end %> + <%= link_to edit_training_resource_path(training_resource), class: "btn btn-secondary btn-sm" do %> + Edit + <% end %> + <%= link_to training_resource, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger btn-sm" do %> + Delete + <% end %> +
+
+
+
+
+
+
+
+ + diff --git a/app/views/training_resources/index.json.jbuilder b/app/views/training_resources/index.json.jbuilder new file mode 100644 index 00000000..cb5ae155 --- /dev/null +++ b/app/views/training_resources/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @training_resources, partial: "training_resources/training_resource", as: :training_resource diff --git a/app/views/training_resources/new.html.erb b/app/views/training_resources/new.html.erb new file mode 100644 index 00000000..5296ee9c --- /dev/null +++ b/app/views/training_resources/new.html.erb @@ -0,0 +1,23 @@ +<% content_for :title, "New training resource" %> + +
+
+
+
+
+

Create Training Resource

+
+
+
+

Regions should ... Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est + laborum.

+ <%= render "form", training_resource: @training_resource %> +
+
+
+
+
+
diff --git a/app/views/training_resources/show.html.erb b/app/views/training_resources/show.html.erb new file mode 100644 index 00000000..10763616 --- /dev/null +++ b/app/views/training_resources/show.html.erb @@ -0,0 +1,12 @@ +

<%= notice %>

+ +<%= render @training_resource %> + +
+ <%= link_to "Edit this training resource", edit_training_resource_path(@training_resource) %> | + <%= link_to "Back to training resources", training_resources_path %> +
+ +
+ <%= button_to "Destroy this training resource", @training_resource, method: :delete, class: "btn btn-danger mt-4" %> +
diff --git a/app/views/training_resources/show.json.jbuilder b/app/views/training_resources/show.json.jbuilder new file mode 100644 index 00000000..c0b3a8c0 --- /dev/null +++ b/app/views/training_resources/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "training_resources/training_resource", training_resource: @training_resource diff --git a/config/routes.rb b/config/routes.rb index f74514b6..56bbf47f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,7 @@ resources :regions resource :registration, only: %i[new create] resource :session + resources :training_resources resources :users resources :topics do put :archive, on: :member diff --git a/db/migrate/20250204124356_create_active_storage_tables.active_storage.rb b/db/migrate/20250204124356_create_active_storage_tables.active_storage.rb new file mode 100644 index 00000000..6bd8bd08 --- /dev/null +++ b/db/migrate/20250204124356_create_active_storage_tables.active_storage.rb @@ -0,0 +1,57 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + # Use Active Record's configured type for primary and foreign keys + primary_key_type, foreign_key_type = primary_and_foreign_key_types + + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [ primary_key_type, foreign_key_type ] + end +end diff --git a/db/migrate/20250204130826_create_training_resources.rb b/db/migrate/20250204130826_create_training_resources.rb new file mode 100644 index 00000000..36abd196 --- /dev/null +++ b/db/migrate/20250204130826_create_training_resources.rb @@ -0,0 +1,11 @@ +class CreateTrainingResources < ActiveRecord::Migration[8.0] + def change + create_table :training_resources do |t| + t.integer :state + t.string :file_name_override + t.references :topic + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b1ce22c5..122571e1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,6 +14,34 @@ # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum" + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "branches", force: :cascade do |t| t.bigint "provider_id" t.bigint "region_id" @@ -74,6 +102,15 @@ t.index ["provider_id"], name: "index_topics_on_provider_id" end + create_table "training_resources", force: :cascade do |t| + t.integer "state" + t.string "file_name_override" + t.bigint "topic_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["topic_id"], name: "index_training_resources_on_topic_id" + end + create_table "users", force: :cascade do |t| t.string "email", null: false t.string "password_digest", null: false @@ -83,5 +120,7 @@ t.index ["email"], name: "index_users_on_email", unique: true end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "sessions", "users" end diff --git a/spec/factories/providers.rb b/spec/factories/providers.rb index f7369f5b..2d0310ad 100644 --- a/spec/factories/providers.rb +++ b/spec/factories/providers.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :provider do - name { "ACME" } + sequence(:name) { |n| "provider_#{n}" } provider_type { "provider" } end end diff --git a/spec/factories/regions.rb b/spec/factories/regions.rb index 3d2de86d..cd433df3 100644 --- a/spec/factories/regions.rb +++ b/spec/factories/regions.rb @@ -1,5 +1,5 @@ FactoryBot.define do factory :region do - name { "The Region" } + name { Faker::Address.state } end end diff --git a/spec/factories/training_resources.rb b/spec/factories/training_resources.rb new file mode 100644 index 00000000..4cb18926 --- /dev/null +++ b/spec/factories/training_resources.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :training_resource do + sequence(:file_name_override) { |n| "file_name_override_#{n}.jpg" } + document { Rack::Test::UploadedFile.new("spec/support/images/logo_ruby_for_good.png", "image/png") } + state { 1 } + topic + end +end diff --git a/spec/models/training_resource_spec.rb b/spec/models/training_resource_spec.rb new file mode 100644 index 00000000..36dc86cf --- /dev/null +++ b/spec/models/training_resource_spec.rb @@ -0,0 +1,20 @@ +require "rails_helper" + +RSpec.describe TrainingResource, type: :model do + subject { create(:training_resource) } + it { should validate_presence_of(:state) } + it { should have_one_attached(:document) } + + it "should reject creation of resources with duplicated filenames within the same language" do + resource = create(:training_resource, file_name_override: "filename.pdf") + expect { + create(:training_resource, file_name_override: "filename.pdf", topic_id: resource.topic_id) + }.to raise_error(ActiveRecord::RecordInvalid) + end + + it "should allow creation of resources without file_name_override" do + expect { + create(:training_resource, file_name_override: nil) + }.to change(TrainingResource, :count).by(1) + end +end diff --git a/spec/requests/training_resources/create_spec.rb b/spec/requests/training_resources/create_spec.rb new file mode 100644 index 00000000..684c2c31 --- /dev/null +++ b/spec/requests/training_resources/create_spec.rb @@ -0,0 +1,58 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "POST /training_resources" do + let(:user) { create(:user) } + let(:topic) { create(:topic) } + let(:valid_attributes) { attributes_for(:training_resource, topic_id: topic.id) } + let(:invalid_attributes) { { document: "", file_name_override: "", state: "", topic_id: "" } } + + before { sign_in(user) } + + context "with valid parameters" do + it "creates a new TrainingResource" do + expect { + post training_resources_url, params: { training_resource: valid_attributes } + }.to change(TrainingResource, :count).by(1) + end + + it "redirects to the created training_resource" do + post training_resources_url, params: { training_resource: valid_attributes } + expect(response).to redirect_to(training_resource_url(TrainingResource.last)) + end + end + + context "with invalid parameters" do + it "does not create a new TrainingResource" do + expect { + post training_resources_url, params: { training_resource: invalid_attributes } + }.to change(TrainingResource, :count).by(0) + end + + it "renders a response with 422 status (i.e. to display the 'new' template)" do + post training_resources_url, params: { training_resource: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + + context "with file_name_override present" do + it "does not create a new TrainingResource if there is already a preexistent entry with the same language" do + preexistent_topic = create(:topic, language_id: topic.language_id) + TrainingResource.create! valid_attributes + + post training_resources_url, params: { training_resource: valid_attributes.merge(topic_id: preexistent_topic.id) } + + expect(response).to have_http_status(:unprocessable_entity) + end + + it "creates a new Training Resource if file_name_override has a different language" do + preexistent_topic = create(:topic) + TrainingResource.create! valid_attributes + + post training_resources_url, params: { training_resource: valid_attributes.merge(topic_id: preexistent_topic.id) } + + expect(response).to have_http_status(:redirect) + end + end + end +end diff --git a/spec/requests/training_resources/destroy_spec.rb b/spec/requests/training_resources/destroy_spec.rb new file mode 100644 index 00000000..14528f89 --- /dev/null +++ b/spec/requests/training_resources/destroy_spec.rb @@ -0,0 +1,24 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "DELETE /destroy" do + let(:user) { create(:user) } + let(:topic) { create(:topic) } + let(:valid_attributes) { attributes_for(:training_resource, topic_id: topic.id) } + + before { sign_in(user) } + + it "destroys the requested training_resource" do + training_resource = TrainingResource.create! valid_attributes + expect { + delete training_resource_url(training_resource) + }.to change(TrainingResource, :count).by(-1) + end + + it "redirects to the training_resources list" do + training_resource = TrainingResource.create! valid_attributes + delete training_resource_url(training_resource) + expect(response).to redirect_to(training_resources_url) + end + end +end diff --git a/spec/requests/training_resources/edit_spec.rb b/spec/requests/training_resources/edit_spec.rb new file mode 100644 index 00000000..f594ea77 --- /dev/null +++ b/spec/requests/training_resources/edit_spec.rb @@ -0,0 +1,17 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "GET /training_resources/:id" do + let(:user) { create(:user) } + + before { sign_in(user) } + + it "renders a successful response" do + training_resource = create(:training_resource) + + get edit_training_resource_url(training_resource) + + expect(response).to be_successful + end + end +end diff --git a/spec/requests/training_resources/index_spec.rb b/spec/requests/training_resources/index_spec.rb new file mode 100644 index 00000000..7d12bb91 --- /dev/null +++ b/spec/requests/training_resources/index_spec.rb @@ -0,0 +1,18 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "GET /training_resources" do + let(:user) { create(:user) } + + before { sign_in(user) } + + it "renders a successful response" do + create(:training_resource) + + get training_resources_url + + expect(response).to be_successful + expect(assigns(:training_resources)).to eq(TrainingResource.all) + end + end +end diff --git a/spec/requests/training_resources/new_spec.rb b/spec/requests/training_resources/new_spec.rb new file mode 100644 index 00000000..e2a72525 --- /dev/null +++ b/spec/requests/training_resources/new_spec.rb @@ -0,0 +1,15 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "GET /training_resources/new" do + let(:user) { create(:user) } + + before { sign_in(user) } + + it "renders a successful response" do + get new_training_resource_url + + expect(response).to be_successful + end + end +end diff --git a/spec/requests/training_resources/show_spec.rb b/spec/requests/training_resources/show_spec.rb new file mode 100644 index 00000000..6457dd4f --- /dev/null +++ b/spec/requests/training_resources/show_spec.rb @@ -0,0 +1,17 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "GET /training_resources/:id" do + let(:user) { create(:user) } + + before { sign_in(user) } + + it "renders a successful response" do + training_resource = create(:training_resource) + + get training_resource_url(training_resource) + + expect(response).to be_successful + end + end +end diff --git a/spec/requests/training_resources/update_spec.rb b/spec/requests/training_resources/update_spec.rb new file mode 100644 index 00000000..b5137c02 --- /dev/null +++ b/spec/requests/training_resources/update_spec.rb @@ -0,0 +1,40 @@ +require "rails_helper" + +RSpec.describe "Training Resources", type: :request do + describe "PATCH /training_resources" do + let(:user) { create(:user) } + let(:topic) { create(:topic) } + let(:valid_attributes) { attributes_for(:training_resource, topic_id: topic.id) } + let(:invalid_attributes) { { document: "", file_name_override: "", state: "", topic_id: "" } } + + before { sign_in(user) } + + context "with valid parameters" do + let(:new_attributes) { + { state: 1 } + } + + it "updates the requested training_resource" do + training_resource = TrainingResource.create! valid_attributes + patch training_resource_url(training_resource), params: { training_resource: new_attributes } + training_resource.reload + expect(response).to redirect_to(training_resource_url(training_resource)) + end + + it "redirects to the training_resource" do + training_resource = TrainingResource.create! valid_attributes + patch training_resource_url(training_resource), params: { training_resource: new_attributes } + training_resource.reload + expect(response).to redirect_to(training_resource_url(training_resource)) + end + end + + context "with invalid parameters" do + it "renders a response with 422 status (i.e. to display the 'edit' template)" do + training_resource = TrainingResource.create! valid_attributes + patch training_resource_url(training_resource), params: { training_resource: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end +end diff --git a/spec/routing/training_resources_routing_spec.rb b/spec/routing/training_resources_routing_spec.rb new file mode 100644 index 00000000..5c79ad4e --- /dev/null +++ b/spec/routing/training_resources_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe TrainingResourcesController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/training_resources").to route_to("training_resources#index") + end + + it "routes to #new" do + expect(get: "/training_resources/new").to route_to("training_resources#new") + end + + it "routes to #show" do + expect(get: "/training_resources/1").to route_to("training_resources#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/training_resources/1/edit").to route_to("training_resources#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/training_resources").to route_to("training_resources#create") + end + + it "routes to #update via PUT" do + expect(put: "/training_resources/1").to route_to("training_resources#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/training_resources/1").to route_to("training_resources#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/training_resources/1").to route_to("training_resources#destroy", id: "1") + end + end +end diff --git a/spec/support/images/logo_ruby_for_good.png b/spec/support/images/logo_ruby_for_good.png new file mode 100644 index 00000000..951192c7 Binary files /dev/null and b/spec/support/images/logo_ruby_for_good.png differ diff --git a/spec/views/regions/index.html.erb_spec.rb b/spec/views/regions/index.html.erb_spec.rb index e8ad23ec..fe3561aa 100644 --- a/spec/views/regions/index.html.erb_spec.rb +++ b/spec/views/regions/index.html.erb_spec.rb @@ -2,18 +2,12 @@ RSpec.describe "regions/index", type: :view do before(:each) do - assign( - :regions, - [ - Region.create!(name: "Name"), - Region.create!(name: "Name"), - ], - ) + assign(:regions, [ create(:region, name: "Region 1"), create(:region, name: "Region 2") ]) end it "renders a list of regions" do render cell_selector = "table>tbody>tr" - assert_select cell_selector, text: Regexp.new("Name".to_s), count: 2 + assert_select cell_selector, text: Regexp.new(/Region/), count: 2 end end diff --git a/spec/views/training_resources/edit.html.erb_spec.rb b/spec/views/training_resources/edit.html.erb_spec.rb new file mode 100644 index 00000000..2c62a682 --- /dev/null +++ b/spec/views/training_resources/edit.html.erb_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +RSpec.describe "training_resources/edit", type: :view do + let(:topic) { create(:topic) } + let(:training_resource) { + TrainingResource.create!( + state: 1, + document: nil, + topic: topic, + file_name_override: "test.jpg", + ) + } + + before(:each) do + assign(:training_resource, training_resource) + end + + it "renders the edit training_resource form" do + render + + assert_select "form[action=?][method=?]", training_resource_path(training_resource), "post" do + assert_select "input[name=?]", "training_resource[state]" + + assert_select "input[name=?]", "training_resource[document]" + end + end +end diff --git a/spec/views/training_resources/index.html.erb_spec.rb b/spec/views/training_resources/index.html.erb_spec.rb new file mode 100644 index 00000000..70f09455 --- /dev/null +++ b/spec/views/training_resources/index.html.erb_spec.rb @@ -0,0 +1,20 @@ +require "rails_helper" + +RSpec.describe "training_resources/index", type: :view do + before(:each) do + assign(:training_resources, [ + create(:training_resource, state: 1, file_name_override: "filename_1.jpg"), + create(:training_resource, state: 2, file_name_override: "filename_2.jpg"), + ],) + end + + it "renders a list of training_resources" do + create(:training_resource) + + cell_selector = "table>tbody>tr" + + render + + assert_select cell_selector, text: Regexp.new(/filename_/), count: 2 + end +end diff --git a/spec/views/training_resources/new.html.erb_spec.rb b/spec/views/training_resources/new.html.erb_spec.rb new file mode 100644 index 00000000..6b86f731 --- /dev/null +++ b/spec/views/training_resources/new.html.erb_spec.rb @@ -0,0 +1,20 @@ +require "rails_helper" + +RSpec.describe "training_resources/new", type: :view do + before(:each) do + assign(:training_resource, TrainingResource.new( + state: 1, + document: nil, + ),) + end + + it "renders new training_resource form" do + render + + assert_select "form[action=?][method=?]", training_resources_path, "post" do + assert_select "input[name=?]", "training_resource[state]" + + assert_select "input[name=?]", "training_resource[document]" + end + end +end diff --git a/spec/views/training_resources/show.html.erb_spec.rb b/spec/views/training_resources/show.html.erb_spec.rb new file mode 100644 index 00000000..108b0d37 --- /dev/null +++ b/spec/views/training_resources/show.html.erb_spec.rb @@ -0,0 +1,13 @@ +require "rails_helper" + +RSpec.describe "training_resources/show", type: :view do + before(:each) do + assign(:training_resource, create(:training_resource)) + end + + it "renders attributes in

" do + render + expect(rendered).to match(/2/) + expect(rendered).to match(//) + end +end