diff --git a/Gemfile.lock b/Gemfile.lock index 0f92f19..08926e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,7 +97,7 @@ GEM activesupport concurrent-ruby (1.3.8) connection_pool (3.0.2) - crass (1.0.7) + crass (1.0.8) cronex (0.15.0) tzinfo unicode (>= 0.4.4.5) @@ -417,7 +417,7 @@ CHECKSUMS case_transform (0.2) sha256=e2ad4418dceeb227cf474cc332cd5004c95c136c04186c1cceaad8ab8de6fe3b concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1 connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a - crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295 + crass (1.0.8) sha256=7a0145c7a2c59f8e7923b3a69ce819c444823a00a5127d805298fea13be24efe cronex (0.15.0) sha256=21c794e085fad2951c4f2e279f440340a35ba2297e0b738f22f263f69fbe2186 date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6 diff --git a/app/controllers/api/v1/feeds_controller.rb b/app/controllers/api/v1/feeds_controller.rb new file mode 100644 index 0000000..13bf07d --- /dev/null +++ b/app/controllers/api/v1/feeds_controller.rb @@ -0,0 +1,42 @@ +module Api + module V1 + class FeedsController < BaseController + before_action :authenticate_request! + + def create + feed = Feeds::Create.call(name: feed_params[:name], initial_stock_quantity: feed_params[:initial_stock_quantity]) + render json: feed, serializer: FeedSerializer, status: :created + end + + def index + result = Feeds::List.call( + filters: params.permit(:name, :min_stock_quantity, :max_stock_quantity).to_h, + sort: params[:sort], + direction: params[:direction], + page: params[:page], per_page: params[:per_page]) + render json: PaginatedCollectionSerializer.call(result:, serializer: FeedSerializer) + end + + def show + feed = Feeds::Find.call(id: params[:id]) + render json: feed, serializer: FeedSerializer + end + + def update + feed = Feeds::Update.call(id: params[:id], name: feed_params[:name]) + render json: feed, serializer: FeedSerializer + end + + def destroy + Feeds::Deactivate.call(id: params[:id]) + head :no_content + end + + private + + def feed_params + params.require(:feed).permit(:name, :initial_stock_quantity) + end + end + end +end diff --git a/app/models/feed.rb b/app/models/feed.rb new file mode 100644 index 0000000..7ccd502 --- /dev/null +++ b/app/models/feed.rb @@ -0,0 +1,15 @@ +class Feed < ApplicationRecord + validates :name, presence: true + validates :stock_quantity, presence: true, numericality: { greater_than_or_equal_to: 0 } + + scope :active, -> { where(deleted_at: nil) } + scope :deleted, -> { where.not(deleted_at: nil) } + + def deleted? + deleted_at.present? + end + + def soft_delete! + update!(deleted_at: Time.current) + end +end diff --git a/app/queries/feeds/query.rb b/app/queries/feeds/query.rb new file mode 100644 index 0000000..aac288c --- /dev/null +++ b/app/queries/feeds/query.rb @@ -0,0 +1,24 @@ +module Feeds + class Query + FILTERS = { + name: ->(scope, value) { scope.where("name ILIKE ?", "%#{value}%") }, + min_stock_quantity: ->(scope, value) { scope.where("stock_quantity >= ?", value) }, + max_stock_quantity: ->(scope, value) { scope.where("stock_quantity <= ?", value) } + }.freeze + + SORTS = { + name: :name, + stock_quantity: :stock_quantity, + created_at: :created_at + }.freeze + + DEFAULT_SORT = { updated_at: :desc }.freeze + + class << self + def call(filters: {}, sort: nil, direction: nil) + scope = Filtering::Filterer.call(scope: Feed.active, filters:, definitions: FILTERS) + Sorting::Sorter.call(scope: scope, sort:, direction:, allowed: SORTS, default: DEFAULT_SORT) + end + end + end +end diff --git a/app/serializers/feed_serializer.rb b/app/serializers/feed_serializer.rb new file mode 100644 index 0000000..7ddc67f --- /dev/null +++ b/app/serializers/feed_serializer.rb @@ -0,0 +1,3 @@ +class FeedSerializer < ActiveModel::Serializer + attributes :id, :name, :stock_quantity, :created_at, :updated_at +end diff --git a/app/services/feeds/create.rb b/app/services/feeds/create.rb new file mode 100644 index 0000000..c233481 --- /dev/null +++ b/app/services/feeds/create.rb @@ -0,0 +1,9 @@ +module Feeds + class Create + class << self + def call(name:, initial_stock_quantity:) + Feed.create!(name:, stock_quantity: initial_stock_quantity) + end + end + end +end diff --git a/app/services/feeds/deactivate.rb b/app/services/feeds/deactivate.rb new file mode 100644 index 0000000..02b6198 --- /dev/null +++ b/app/services/feeds/deactivate.rb @@ -0,0 +1,11 @@ +module Feeds + class Deactivate + class << self + def call(id:) + feed = Feeds::Find.call(id:) + feed.soft_delete! + feed + end + end + end +end diff --git a/app/services/feeds/find.rb b/app/services/feeds/find.rb new file mode 100644 index 0000000..37df0fb --- /dev/null +++ b/app/services/feeds/find.rb @@ -0,0 +1,9 @@ +module Feeds + class Find + class << self + def call(id:) + Feed.active.find(id) + end + end + end +end diff --git a/app/services/feeds/list.rb b/app/services/feeds/list.rb new file mode 100644 index 0000000..335607e --- /dev/null +++ b/app/services/feeds/list.rb @@ -0,0 +1,14 @@ +module Feeds + class List + class << self + def call(filters: {}, sort: nil, direction: nil, page:, per_page:) + scope = Feeds::Query.call(filters:, sort:, direction:) + Pagination::Paginator.call( + scope: scope, + page:, + per_page: + ) + end + end + end +end diff --git a/app/services/feeds/update.rb b/app/services/feeds/update.rb new file mode 100644 index 0000000..b096554 --- /dev/null +++ b/app/services/feeds/update.rb @@ -0,0 +1,11 @@ +module Feeds + class Update + class << self + def call(id:, name:) + feed = Feeds::Find.call(id:) + feed.update!(name:) + feed + end + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 014a79d..5939c38 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -28,6 +28,9 @@ en: terrain: one: "Terrain" other: "Terrains" + feed: + one: "Feed" + other: "Feeds" attributes: user: name: "Name" @@ -41,3 +44,6 @@ en: name: "Name" rest_days: "Rest Days" status: "Status" + feed: + name: "Name" + stock_quantity: "Stock Quantity" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index ce76340..bf65159 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -33,6 +33,9 @@ pt-BR: terrain: one: "Terreno" other: "Terrenos" + feed: + one: "Ração" + other: "Rações" attributes: user: name: "Nome" @@ -46,3 +49,6 @@ pt-BR: name: "Nome" rest_days: "Dias de Descanso" status: "Status" + feed: + name: "Nome" + stock_quantity: "Quantidade em Estoque" diff --git a/config/routes.rb b/config/routes.rb index 080a0a7..e3f14e4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,6 +27,8 @@ resources :occupations, only: [ :create ] + resources :feeds, only: [ :create, :index, :show, :update, :destroy ] + get "health", to: "health#show" end end diff --git a/db/migrate/20261024090253_create_feeds.rb b/db/migrate/20261024090253_create_feeds.rb new file mode 100644 index 0000000..78af75a --- /dev/null +++ b/db/migrate/20261024090253_create_feeds.rb @@ -0,0 +1,13 @@ +class CreateFeeds < ActiveRecord::Migration[8.1] + def change + create_table :feeds, id: :uuid do |t| + t.string :name, null: false + t.decimal :stock_quantity, precision: 12, scale: 2, null: false, default: 0 + t.datetime :deleted_at + + t.timestamps + end + + add_index :feeds, :deleted_at + end +end diff --git a/db/schema.rb b/db/schema.rb index c4d8d3c..5574f05 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_10_22_181113) do +ActiveRecord::Schema[8.1].define(version: 2026_10_24_090253) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" enable_extension "pgcrypto" + create_table "feeds", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "name", null: false + t.decimal "stock_quantity", precision: 12, scale: 2, default: "0.0", null: false + t.datetime "deleted_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["deleted_at"], name: "index_feeds_on_deleted_at" + end + create_table "groups", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "name", null: false t.integer "animal_count", null: false diff --git a/spec/factories/feeds.rb b/spec/factories/feeds.rb new file mode 100644 index 0000000..c3583d5 --- /dev/null +++ b/spec/factories/feeds.rb @@ -0,0 +1,11 @@ +FactoryBot.define do + factory :feed do + name { Faker::Food.ingredient } + stock_quantity { Faker::Number.decimal(l_digits: 3, r_digits: 2) } + deleted_at { nil } + + trait :deleted do + deleted_at { Time.current } + end + end +end diff --git a/spec/models/feed_spec.rb b/spec/models/feed_spec.rb new file mode 100644 index 0000000..acb146d --- /dev/null +++ b/spec/models/feed_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +RSpec.describe Feed, type: :model do + describe 'validations' do + it "is valid with valid attributes" do + feed = build(:feed) + expect(feed).to be_valid + end + + it "is not valid without a name" do + feed = build(:feed, name: nil) + expect(feed).not_to be_valid + end + + it "is not valid without a stock_quantity" do + feed = build(:feed, stock_quantity: nil) + expect(feed).not_to be_valid + end + + it "is not valid with a negative stock_quantity" do + feed = build(:feed, stock_quantity: -1.0) + expect(feed).not_to be_valid + end + end + + describe '.active' do + it "returns only active feeds" do + active_feed = create(:feed) + create(:feed, :deleted) + expect(described_class.active).to contain_exactly(active_feed) + end + end + + describe '.deleted' do + it "returns only deleted feeds" do + create(:feed) + deleted_feed = create(:feed, :deleted) + expect(described_class.deleted).to contain_exactly(deleted_feed) + end + end + + describe '#deleted?' do + it "returns true if the feed is deleted" do + feed = build(:feed, :deleted) + expect(feed.deleted?).to be true + end + + it "returns false if the feed is not deleted" do + feed = build(:feed) + expect(feed.deleted?).to be false + end + end + + describe '#soft_delete!' do + it "marks the feed as deleted" do + feed = create(:feed) + feed.soft_delete! + expect(feed.deleted?).to be true + end + end +end diff --git a/spec/requests/api/v1/feeds/create_spec.rb b/spec/requests/api/v1/feeds/create_spec.rb new file mode 100644 index 0000000..0de2756 --- /dev/null +++ b/spec/requests/api/v1/feeds/create_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +RSpec.describe "POST /api/v1/feeds", type: :request do + let (:user) { create(:user) } + let (:tokens) { Auth::CreateSession.call(user:) } + + let(:headers) do + { + "Authorization" => "Bearer #{tokens[:access_token]}" + } + end + + let(:valid_params) { { feed: { name: "Feed A", initial_stock_quantity: 100.5 } } } + + it "creates a feed with valid attributes" do + post "/api/v1/feeds", params: valid_params, headers: headers, as: :json + expect(response).to have_http_status(:created) + expect(response.parsed_body).to include( + "id" => be_present, + "name" => "Feed A", + "stock_quantity" => "100.5", + ) + end + + it "returns validation errors with invalid attributes" do + post "/api/v1/feeds", params: { feed: { name: nil, initial_stock_quantity: -100 } }, headers: headers, as: :json + expect(response).to have_http_status(:unprocessable_content) + expect(response.parsed_body["errors"]["name"]).to be_present + end + + it "returns unauthorized without valid authentication" do + post "/api/v1/feeds", params: valid_params, as: :json + expect(response).to have_http_status(:unauthorized) + end +end diff --git a/spec/requests/api/v1/feeds/destroy_spec.rb b/spec/requests/api/v1/feeds/destroy_spec.rb new file mode 100644 index 0000000..ed49e66 --- /dev/null +++ b/spec/requests/api/v1/feeds/destroy_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +RSpec.describe "DELETE /api/v1/feeds/:id", type: :request do + let (:user) { create(:user) } + let (:tokens) { Auth::CreateSession.call(user:) } + + let(:headers) do + { + "Authorization" => "Bearer #{tokens[:access_token]}" + } + end + + it "soft deletes the feed" do + feed = create(:feed) + delete "/api/v1/feeds/#{feed.id}", headers: headers + expect(response).to have_http_status(:no_content) + expect(feed.reload).to be_deleted + end + + it "returns not found when the feed does not exist" do + delete "/api/v1/feeds/#{SecureRandom.uuid}", headers: headers + expect(response).to have_http_status(:not_found) + end + + it "returns not found when the feed is already deleted" do + feed = create(:feed, :deleted) + delete "/api/v1/feeds/#{feed.id}", headers: headers + expect(response).to have_http_status(:not_found) + end + + it "returns unauthorized when no token is provided" do + feed = create(:feed) + delete "/api/v1/feeds/#{feed.id}" + expect(response).to have_http_status(:unauthorized) + end +end diff --git a/spec/requests/api/v1/feeds/index_spec.rb b/spec/requests/api/v1/feeds/index_spec.rb new file mode 100644 index 0000000..608cac5 --- /dev/null +++ b/spec/requests/api/v1/feeds/index_spec.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe "GET /api/v1/feeds", type: :request do + let (:user) { create(:user) } + let (:tokens) { Auth::CreateSession.call(user:) } + + let(:headers) do + { + "Authorization" => "Bearer #{tokens[:access_token]}" + } + end + + it "filters feeds" do + matching_feed = create(:feed, name: "Premium Feed") + create(:feed, name: "Basic Feed") + get "/api/v1/feeds", headers: headers, params: { name: "Premium" } + body = response.parsed_body + expect(response).to have_http_status(:ok) + expect(body['data'].pluck('id')).to eq([ matching_feed.id ]) + end + + it "sorts feeds" do + low_stock_feed = create(:feed, stock_quantity: 5) + high_stock_feed = create(:feed, stock_quantity: 20) + get "/api/v1/feeds", headers: headers, params: { sort: "stock_quantity", direction: "desc" } + body = response.parsed_body + expect(response).to have_http_status(:ok) + expect(body['data'].pluck('id')).to eq([ high_stock_feed.id, low_stock_feed.id ]) + end + + it "returns a paginated list of feeds" do + create_list(:feed, 3) + get "/api/v1/feeds", headers: headers, params: { page: 1, per_page: 2 } + body = response.parsed_body + expect(response).to have_http_status(:ok) + expect(body['data'].size).to eq(2) + expect(body['meta']).to include( + 'page' => 1, + 'per_page' => 2, + 'total_items' => 3, + 'total_pages' => 2 + ) + end +end diff --git a/spec/requests/api/v1/feeds/show_spec.rb b/spec/requests/api/v1/feeds/show_spec.rb new file mode 100644 index 0000000..765a254 --- /dev/null +++ b/spec/requests/api/v1/feeds/show_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +RSpec.describe "GET /api/v1/feeds/:id", type: :request do + let (:user) { create(:user) } + let (:tokens) { Auth::CreateSession.call(user:) } + + let(:headers) do + { + "Authorization" => "Bearer #{tokens[:access_token]}" + } + end + + it "returns a feed with the given id" do + feed = create(:feed) + get "/api/v1/feeds/#{feed.id}", headers: headers + body = response.parsed_body + expect(response).to have_http_status(:ok) + expect(body).to include( + "id" => feed.id, + "name" => feed.name, + "stock_quantity" => feed.stock_quantity.to_s("F") + ) + end + + it "returns not found when the feed does not exist" do + get "/api/v1/feeds/#{SecureRandom.uuid}", headers: headers + expect(response).to have_http_status(:not_found) + end + + it "returns unauthorized when no token is provided" do + feed = create(:feed) + get "/api/v1/feeds/#{feed.id}" + expect(response).to have_http_status(:unauthorized) + end +end diff --git a/spec/requests/api/v1/feeds/update_spec.rb b/spec/requests/api/v1/feeds/update_spec.rb new file mode 100644 index 0000000..45214b5 --- /dev/null +++ b/spec/requests/api/v1/feeds/update_spec.rb @@ -0,0 +1,56 @@ +require 'rails_helper' + +RSpec.describe "PATCH /api/v1/feeds/:id", type: :request do + let (:user) { create(:user) } + let (:tokens) { Auth::CreateSession.call(user:) } + + let(:headers) do + { + "Authorization" => "Bearer #{tokens[:access_token]}" + } + end + + it "updates the feed name" do + feed = create(:feed, name: "Old Name") + patch "/api/v1/feeds/#{feed.id}", params: { feed: { name: "New Name" } }, headers: headers, as: :json + body = response.parsed_body + expect(response).to have_http_status(:ok) + expect(body).to include( + "id" => feed.id, + "name" => "New Name", + "stock_quantity" => feed.stock_quantity.to_s("F") + ) + expect(feed.reload.name).to eq("New Name") + end + + it "does not update the stock quantity directly" do + feed = create(:feed, stock_quantity: 10) + patch "/api/v1/feeds/#{feed.id}", params: { feed: { name: feed.name, stock_quantity: 20 } }, headers: headers, as: :json + body = response.parsed_body + expect(response).to have_http_status(:ok) + expect(body).to include( + "id" => feed.id, + "name" => feed.name, + "stock_quantity" => feed.stock_quantity.to_s("F") + ) + expect(feed.reload.stock_quantity).to eq(10) + end + + it "returns validation errors when the attributes are invalid" do + feed = create(:feed) + patch "/api/v1/feeds/#{feed.id}", params: { feed: { name: "" } }, headers: headers, as: :json + expect(response).to have_http_status(:unprocessable_content) + expect(response.parsed_body["errors"]["name"]).to be_present + end + + it "returns not found when the feed does not exist" do + patch "/api/v1/feeds/#{SecureRandom.uuid}", params: { feed: { name: "New Name" } }, headers: headers, as: :json + expect(response).to have_http_status(:not_found) + end + + it "returns unauthorized when no token is provided" do + feed = create(:feed) + patch "/api/v1/feeds/#{feed.id}", params: { feed: { name: "New Name" } }, as: :json + expect(response).to have_http_status(:unauthorized) + end +end diff --git a/spec/services/feeds/create_spec.rb b/spec/services/feeds/create_spec.rb new file mode 100644 index 0000000..b24ab3c --- /dev/null +++ b/spec/services/feeds/create_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.describe Feeds::Create, type: :service do + describe '.call' do + it "creates a feed with initial stock quantity" do + feed = described_class.call(name: "Feed A", initial_stock_quantity: 100) + expect(feed).to be_persisted + expect(feed.name).to eq("Feed A") + expect(feed.stock_quantity).to eq(100) + end + + it "raises an error if the feed cannot be created" do + expect { + described_class.call(name: nil, initial_stock_quantity: 100) + }.to raise_error(ActiveRecord::RecordInvalid) + end + end +end diff --git a/spec/services/feeds/deactivate_spec.rb b/spec/services/feeds/deactivate_spec.rb new file mode 100644 index 0000000..07831e3 --- /dev/null +++ b/spec/services/feeds/deactivate_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +RSpec.describe Feeds::Deactivate, type: :service do + describe '.call' do + it 'deactivates the feed' do + feed = create(:feed) + result = described_class.call(id: feed.id) + expect(result.reload).to be_deleted + end + + it 'raises an error when the feed is already deleted' do + feed = create(:feed, :deleted) + expect { + described_class.call(id: feed.id) + }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises an error when the feed does not exist' do + expect { + described_class.call(id: SecureRandom.uuid) + }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end diff --git a/spec/services/feeds/find_spec.rb b/spec/services/feeds/find_spec.rb new file mode 100644 index 0000000..643d4e8 --- /dev/null +++ b/spec/services/feeds/find_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe Feeds::Find, type: :service do + describe '.call' do + it 'returns an active feed' do + feed = create(:feed) + result = described_class.call(id: feed.id) + expect(result).to eq(feed) + end + + it 'raises an error when feed deleted' do + feed = create(:feed, :deleted) + expect { described_class.call(id: feed.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises an error when feed does not exist' do + expect { described_class.call(id: SecureRandom.uuid) }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end diff --git a/spec/services/feeds/list_spec.rb b/spec/services/feeds/list_spec.rb new file mode 100644 index 0000000..c58f551 --- /dev/null +++ b/spec/services/feeds/list_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +RSpec.describe Feeds::List, type: :service do + describe '.call' do + it "returns only active feeds" do + active_feed = create(:feed) + create(:feed, :deleted) + result = described_class.call(page: 1, per_page: 10) + expect(result.records).to contain_exactly(active_feed) + end + + it "filters feeds by name" do + matching_feed = create(:feed, name: "Premium Feed") + create(:feed, name: "Basic Feed") + result = described_class.call(filters: { name: "Premium" }, page: 1, per_page: 10) + expect(result.records).to contain_exactly(matching_feed) + end + + it "sorts feeds by stock quantity" do + low_stock_feed = create(:feed, stock_quantity: 5) + high_stock_feed = create(:feed, stock_quantity: 20) + result = described_class.call(sort: "stock_quantity", direction: "desc", page: 1, per_page: 10) + expect(result.records).to eq([ high_stock_feed, low_stock_feed ]) + end + + it "returns a paginated result" do + create_list(:feed, 15) + result = described_class.call(page: 2, per_page: 10) + expect(result.records.size).to eq(5) + expect(result.page).to eq(2) + expect(result.per_page).to eq(10) + expect(result.total_items).to eq(15) + expect(result.total_pages).to eq(2) + end + + it "paginates the filtered query results" do + create_list(:feed, 15, name: "Premium Feed") + create_list(:feed, 10, name: "Basic Feed") + result = described_class.call(filters: { name: "Premium" }, page: 1, per_page: 10) + expect(result.records.size).to eq(10) + expect(result.total_items).to eq(15) + expect(result.total_pages).to eq(2) + end + end +end diff --git a/spec/services/feeds/update_spec.rb b/spec/services/feeds/update_spec.rb new file mode 100644 index 0000000..9ed4e4b --- /dev/null +++ b/spec/services/feeds/update_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe Feeds::Update, type: :service do + describe '.call' do + it 'updates the feed name' do + feed = create(:feed, name: "Old Name") + result = described_class.call(id: feed.id, name: "New Name") + expect(result.name).to eq("New Name") + end + + it "persists the changes" do + feed = create(:feed, name: "Old Name") + described_class.call(id: feed.id, name: "New Name") + expect(feed.reload.name).to eq("New Name") + end + + it "raises an error if the name is invalid" do + feed = create(:feed) + expect { + described_class.call(id: feed.id, name: "") + }.to raise_error(ActiveRecord::RecordInvalid) + end + + it 'raises an error when the feed is deleted' do + feed = create(:feed, :deleted) + expect { + described_class.call(id: feed.id, name: "New Name") + }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'raises an error when the feed does not exist' do + expect { + described_class.call(id: SecureRandom.uuid, name: "New Name") + }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end