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:
+
+
+ <% training_resource.errors.each do |error| %>
+ - <%= error.full_message %>
+ <% end %>
+
+
+ <% 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" %>
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+ | Topic |
+ State |
+ Filename Override |
+ Document |
+ Actions |
+
+
+
+ <% @training_resources.each do |training_resource| %>
+
+ | <%= 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 %>
+ |
+
+ <% 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" %>
+
+
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