diff --git a/app/controllers/search_stats_controller.rb b/app/controllers/search_stats_controller.rb index 59edf49..1376cb7 100644 --- a/app/controllers/search_stats_controller.rb +++ b/app/controllers/search_stats_controller.rb @@ -1,8 +1,38 @@ # frozen_string_literal: true +require 'csv' + class SearchStatsController < ApplicationController + ALLOWED_MIME_TYPE = 'text/csv' + MAXIMUM_KEYWORDS = 1000 + # GET /search_stats def index @pagy, @search_stats = pagy(SearchStat.all) end + + def new + search_stat = SearchStat.new + + render :new, locals: { search_stat: search_stat } + end + + def create + csv_file = params[:csv_file] + + raise 'Invalid file type' unless csv_file.content_type == ALLOWED_MIME_TYPE + + csv_file_content = params[:csv_file].read + keywords = CSV.parse(csv_file_content).flatten.compact_blank + + raise 'Invalid file data' unless keywords.any? + raise 'Too many keywords' unless keywords.count <= MAXIMUM_KEYWORDS + + keywords.map do |keyword| + search_stat = SearchStat.new({ keyword: keyword, user_id: current_user.id }) + search_stat.save! + end + + redirect_to search_stats_path + end end diff --git a/app/models/search_stat.rb b/app/models/search_stat.rb index 82c3194..3aeb13f 100644 --- a/app/models/search_stat.rb +++ b/app/models/search_stat.rb @@ -2,5 +2,4 @@ class SearchStat < ApplicationRecord validates :keyword, presence: true - validates :raw_response, presence: true end diff --git a/app/views/search_stats/new.html.erb b/app/views/search_stats/new.html.erb new file mode 100644 index 0000000..c131f00 --- /dev/null +++ b/app/views/search_stats/new.html.erb @@ -0,0 +1,4 @@ +<%= form_with url: '/search_stats', multipart: true do |form| %> + <%= file_field_tag :csv_file, accept: 'text/csv', class: 'form-control' %> + <%= form.submit 'Upload', class: 'btn btn-primary mt-3' %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 3df81d3..b45332f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,5 +8,5 @@ root "home#index" get "/health_check", to: 'health_check#health_check', as: :rails_health_check - resources :search_stats, only: [:index] + resources :search_stats, only: [:index, :create, :new] end diff --git a/db/seeds.rb b/db/seeds.rb index 4ca4fbb..648b5f0 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -8,6 +8,8 @@ require 'fabrication' +Fabricate(:user, email: 'demo@example.com', password: 'nimbleHq1234') + # Generate dummy data for SearchStat 10.times do Fabricate(:search_stat) diff --git a/spec/fixtures/files/invalid_data.csv b/spec/fixtures/files/invalid_data.csv new file mode 100644 index 0000000..b7ec413 --- /dev/null +++ b/spec/fixtures/files/invalid_data.csv @@ -0,0 +1,3 @@ + + + diff --git a/spec/fixtures/files/invalid_type.txt b/spec/fixtures/files/invalid_type.txt new file mode 100644 index 0000000..4715482 --- /dev/null +++ b/spec/fixtures/files/invalid_type.txt @@ -0,0 +1,3 @@ +1st keyword +2nd keyword +3rd keyword diff --git a/spec/fixtures/files/keywords.csv b/spec/fixtures/files/keywords.csv new file mode 100644 index 0000000..4715482 --- /dev/null +++ b/spec/fixtures/files/keywords.csv @@ -0,0 +1,3 @@ +1st keyword +2nd keyword +3rd keyword diff --git a/spec/fixtures/files/too_many_keywords.csv b/spec/fixtures/files/too_many_keywords.csv new file mode 100644 index 0000000..cf1496d --- /dev/null +++ b/spec/fixtures/files/too_many_keywords.csv @@ -0,0 +1,1001 @@ +1st keyword +2nd keyword +3rd keyword +4th keyword +5th keyword +6th keyword +7th keyword +8th keyword +9th keyword +10th keyword +11th keyword +12th keyword +13th keyword +14th keyword +15th keyword +16th keyword +17th keyword +18th keyword +19th keyword +20th keyword +21st keyword +22nd keyword +23rd keyword +24th keyword +25th keyword +26th keyword +27th keyword +28th keyword +29th keyword +30th keyword +31st keyword +32nd keyword +33rd keyword +34th keyword +35th keyword +36th keyword +37th keyword +38th keyword +39th keyword +40th keyword +41st keyword +42nd keyword +43rd keyword +44th keyword +45th keyword +46th keyword +47th keyword +48th keyword +49th keyword +50th keyword +51st keyword +52nd keyword +53rd keyword +54th keyword +55th keyword +56th keyword +57th keyword +58th keyword +59th keyword +60th keyword +61st keyword +62nd keyword +63rd keyword +64th keyword +65th keyword +66th keyword +67th keyword +68th keyword +69th keyword +70th keyword +71st keyword +72nd keyword +73rd keyword +74th keyword +75th keyword +76th keyword +77th keyword +78th keyword +79th keyword +80th keyword +81st keyword +82nd keyword +83rd keyword +84th keyword +85th keyword +86th keyword +87th keyword +88th keyword +89th keyword +90th keyword +91st keyword +92nd keyword +93rd keyword +94th keyword +95th keyword +96th keyword +97th keyword +98th keyword +99th keyword +100th keyword +101st keyword +102nd keyword +103rd keyword +104th keyword +105th keyword +106th keyword +107th keyword +108th keyword +109th keyword +110th keyword +111th keyword +112th keyword +113th keyword +114th keyword +115th keyword +116th keyword +117th keyword +118th keyword +119th keyword +120th keyword +121st keyword +122nd keyword +123rd keyword +124th keyword +125th keyword +126th keyword +127th keyword +128th keyword +129th keyword +130th keyword +131st keyword +132nd keyword +133rd keyword +134th keyword +135th keyword +136th keyword +137th keyword +138th keyword +139th keyword +140th keyword +141st keyword +142nd keyword +143rd keyword +144th keyword +145th keyword +146th keyword +147th keyword +148th keyword +149th keyword +150th keyword +151st keyword +152nd keyword +153rd keyword +154th keyword +155th keyword +156th keyword +157th keyword +158th keyword +159th keyword +160th keyword +161st keyword +162nd keyword +163rd keyword +164th keyword +165th keyword +166th keyword +167th keyword +168th keyword +169th keyword +170th keyword +171st keyword +172nd keyword +173rd keyword +174th keyword +175th keyword +176th keyword +177th keyword +178th keyword +179th keyword +180th keyword +181st keyword +182nd keyword +183rd keyword +184th keyword +185th keyword +186th keyword +187th keyword +188th keyword +189th keyword +190th keyword +191st keyword +192nd keyword +193rd keyword +194th keyword +195th keyword +196th keyword +197th keyword +198th keyword +199th keyword +200th keyword +201st keyword +202nd keyword +203rd keyword +204th keyword +205th keyword +206th keyword +207th keyword +208th keyword +209th keyword +210th keyword +211th keyword +212th keyword +213th keyword +214th keyword +215th keyword +216th keyword +217th keyword +218th keyword +219th keyword +220th keyword +221st keyword +222nd keyword +223rd keyword +224th keyword +225th keyword +226th keyword +227th keyword +228th keyword +229th keyword +230th keyword +231st keyword +232nd keyword +233rd keyword +234th keyword +235th keyword +236th keyword +237th keyword +238th keyword +239th keyword +240th keyword +241st keyword +242nd keyword +243rd keyword +244th keyword +245th keyword +246th keyword +247th keyword +248th keyword +249th keyword +250th keyword +251st keyword +252nd keyword +253rd keyword +254th keyword +255th keyword +256th keyword +257th keyword +258th keyword +259th keyword +260th keyword +261st keyword +262nd keyword +263rd keyword +264th keyword +265th keyword +266th keyword +267th keyword +268th keyword +269th keyword +270th keyword +271st keyword +272nd keyword +273rd keyword +274th keyword +275th keyword +276th keyword +277th keyword +278th keyword +279th keyword +280th keyword +281st keyword +282nd keyword +283rd keyword +284th keyword +285th keyword +286th keyword +287th keyword +288th keyword +289th keyword +290th keyword +291st keyword +292nd keyword +293rd keyword +294th keyword +295th keyword +296th keyword +297th keyword +298th keyword +299th keyword +300th keyword +301st keyword +302nd keyword +303rd keyword +304th keyword +305th keyword +306th keyword +307th keyword +308th keyword +309th keyword +310th keyword +311th keyword +312th keyword +313th keyword +314th keyword +315th keyword +316th keyword +317th keyword +318th keyword +319th keyword +320th keyword +321st keyword +322nd keyword +323rd keyword +324th keyword +325th keyword +326th keyword +327th keyword +328th keyword +329th keyword +330th keyword +331st keyword +332nd keyword +333rd keyword +334th keyword +335th keyword +336th keyword +337th keyword +338th keyword +339th keyword +340th keyword +341st keyword +342nd keyword +343rd keyword +344th keyword +345th keyword +346th keyword +347th keyword +348th keyword +349th keyword +350th keyword +351st keyword +352nd keyword +353rd keyword +354th keyword +355th keyword +356th keyword +357th keyword +358th keyword +359th keyword +360th keyword +361st keyword +362nd keyword +363rd keyword +364th keyword +365th keyword +366th keyword +367th keyword +368th keyword +369th keyword +370th keyword +371st keyword +372nd keyword +373rd keyword +374th keyword +375th keyword +376th keyword +377th keyword +378th keyword +379th keyword +380th keyword +381st keyword +382nd keyword +383rd keyword +384th keyword +385th keyword +386th keyword +387th keyword +388th keyword +389th keyword +390th keyword +391st keyword +392nd keyword +393rd keyword +394th keyword +395th keyword +396th keyword +397th keyword +398th keyword +399th keyword +400th keyword +401st keyword +402nd keyword +403rd keyword +404th keyword +405th keyword +406th keyword +407th keyword +408th keyword +409th keyword +410th keyword +411th keyword +412th keyword +413th keyword +414th keyword +415th keyword +416th keyword +417th keyword +418th keyword +419th keyword +420th keyword +421st keyword +422nd keyword +423rd keyword +424th keyword +425th keyword +426th keyword +427th keyword +428th keyword +429th keyword +430th keyword +431st keyword +432nd keyword +433rd keyword +434th keyword +435th keyword +436th keyword +437th keyword +438th keyword +439th keyword +440th keyword +441st keyword +442nd keyword +443rd keyword +444th keyword +445th keyword +446th keyword +447th keyword +448th keyword +449th keyword +450th keyword +451st keyword +452nd keyword +453rd keyword +454th keyword +455th keyword +456th keyword +457th keyword +458th keyword +459th keyword +460th keyword +461st keyword +462nd keyword +463rd keyword +464th keyword +465th keyword +466th keyword +467th keyword +468th keyword +469th keyword +470th keyword +471st keyword +472nd keyword +473rd keyword +474th keyword +475th keyword +476th keyword +477th keyword +478th keyword +479th keyword +480th keyword +481st keyword +482nd keyword +483rd keyword +484th keyword +485th keyword +486th keyword +487th keyword +488th keyword +489th keyword +490th keyword +491st keyword +492nd keyword +493rd keyword +494th keyword +495th keyword +496th keyword +497th keyword +498th keyword +499th keyword +500th keyword +501st keyword +502nd keyword +503rd keyword +504th keyword +505th keyword +506th keyword +507th keyword +508th keyword +509th keyword +510th keyword +511th keyword +512th keyword +513th keyword +514th keyword +515th keyword +516th keyword +517th keyword +518th keyword +519th keyword +520th keyword +521st keyword +522nd keyword +523rd keyword +524th keyword +525th keyword +526th keyword +527th keyword +528th keyword +529th keyword +530th keyword +531st keyword +532nd keyword +533rd keyword +534th keyword +535th keyword +536th keyword +537th keyword +538th keyword +539th keyword +540th keyword +541st keyword +542nd keyword +543rd keyword +544th keyword +545th keyword +546th keyword +547th keyword +548th keyword +549th keyword +550th keyword +551st keyword +552nd keyword +553rd keyword +554th keyword +555th keyword +556th keyword +557th keyword +558th keyword +559th keyword +560th keyword +561st keyword +562nd keyword +563rd keyword +564th keyword +565th keyword +566th keyword +567th keyword +568th keyword +569th keyword +570th keyword +571st keyword +572nd keyword +573rd keyword +574th keyword +575th keyword +576th keyword +577th keyword +578th keyword +579th keyword +580th keyword +581st keyword +582nd keyword +583rd keyword +584th keyword +585th keyword +586th keyword +587th keyword +588th keyword +589th keyword +590th keyword +591st keyword +592nd keyword +593rd keyword +594th keyword +595th keyword +596th keyword +597th keyword +598th keyword +599th keyword +600th keyword +601st keyword +602nd keyword +603rd keyword +604th keyword +605th keyword +606th keyword +607th keyword +608th keyword +609th keyword +610th keyword +611th keyword +612th keyword +613th keyword +614th keyword +615th keyword +616th keyword +617th keyword +618th keyword +619th keyword +620th keyword +621st keyword +622nd keyword +623rd keyword +624th keyword +625th keyword +626th keyword +627th keyword +628th keyword +629th keyword +630th keyword +631st keyword +632nd keyword +633rd keyword +634th keyword +635th keyword +636th keyword +637th keyword +638th keyword +639th keyword +640th keyword +641st keyword +642nd keyword +643rd keyword +644th keyword +645th keyword +646th keyword +647th keyword +648th keyword +649th keyword +650th keyword +651st keyword +652nd keyword +653rd keyword +654th keyword +655th keyword +656th keyword +657th keyword +658th keyword +659th keyword +660th keyword +661st keyword +662nd keyword +663rd keyword +664th keyword +665th keyword +666th keyword +667th keyword +668th keyword +669th keyword +670th keyword +671st keyword +672nd keyword +673rd keyword +674th keyword +675th keyword +676th keyword +677th keyword +678th keyword +679th keyword +680th keyword +681st keyword +682nd keyword +683rd keyword +684th keyword +685th keyword +686th keyword +687th keyword +688th keyword +689th keyword +690th keyword +691st keyword +692nd keyword +693rd keyword +694th keyword +695th keyword +696th keyword +697th keyword +698th keyword +699th keyword +700th keyword +701st keyword +702nd keyword +703rd keyword +704th keyword +705th keyword +706th keyword +707th keyword +708th keyword +709th keyword +710th keyword +711th keyword +712th keyword +713th keyword +714th keyword +715th keyword +716th keyword +717th keyword +718th keyword +719th keyword +720th keyword +721st keyword +722nd keyword +723rd keyword +724th keyword +725th keyword +726th keyword +727th keyword +728th keyword +729th keyword +730th keyword +731st keyword +732nd keyword +733rd keyword +734th keyword +735th keyword +736th keyword +737th keyword +738th keyword +739th keyword +740th keyword +741st keyword +742nd keyword +743rd keyword +744th keyword +745th keyword +746th keyword +747th keyword +748th keyword +749th keyword +750th keyword +751st keyword +752nd keyword +753rd keyword +754th keyword +755th keyword +756th keyword +757th keyword +758th keyword +759th keyword +760th keyword +761st keyword +762nd keyword +763rd keyword +764th keyword +765th keyword +766th keyword +767th keyword +768th keyword +769th keyword +770th keyword +771st keyword +772nd keyword +773rd keyword +774th keyword +775th keyword +776th keyword +777th keyword +778th keyword +779th keyword +780th keyword +781st keyword +782nd keyword +783rd keyword +784th keyword +785th keyword +786th keyword +787th keyword +788th keyword +789th keyword +790th keyword +791st keyword +792nd keyword +793rd keyword +794th keyword +795th keyword +796th keyword +797th keyword +798th keyword +799th keyword +800th keyword +801st keyword +802nd keyword +803rd keyword +804th keyword +805th keyword +806th keyword +807th keyword +808th keyword +809th keyword +810th keyword +811th keyword +812th keyword +813th keyword +814th keyword +815th keyword +816th keyword +817th keyword +818th keyword +819th keyword +820th keyword +821st keyword +822nd keyword +823rd keyword +824th keyword +825th keyword +826th keyword +827th keyword +828th keyword +829th keyword +830th keyword +831st keyword +832nd keyword +833rd keyword +834th keyword +835th keyword +836th keyword +837th keyword +838th keyword +839th keyword +840th keyword +841st keyword +842nd keyword +843rd keyword +844th keyword +845th keyword +846th keyword +847th keyword +848th keyword +849th keyword +850th keyword +851st keyword +852nd keyword +853rd keyword +854th keyword +855th keyword +856th keyword +857th keyword +858th keyword +859th keyword +860th keyword +861st keyword +862nd keyword +863rd keyword +864th keyword +865th keyword +866th keyword +867th keyword +868th keyword +869th keyword +870th keyword +871st keyword +872nd keyword +873rd keyword +874th keyword +875th keyword +876th keyword +877th keyword +878th keyword +879th keyword +880th keyword +881st keyword +882nd keyword +883rd keyword +884th keyword +885th keyword +886th keyword +887th keyword +888th keyword +889th keyword +890th keyword +891st keyword +892nd keyword +893rd keyword +894th keyword +895th keyword +896th keyword +897th keyword +898th keyword +899th keyword +900th keyword +901st keyword +902nd keyword +903rd keyword +904th keyword +905th keyword +906th keyword +907th keyword +908th keyword +909th keyword +910th keyword +911th keyword +912th keyword +913th keyword +914th keyword +915th keyword +916th keyword +917th keyword +918th keyword +919th keyword +920th keyword +921st keyword +922nd keyword +923rd keyword +924th keyword +925th keyword +926th keyword +927th keyword +928th keyword +929th keyword +930th keyword +931st keyword +932nd keyword +933rd keyword +934th keyword +935th keyword +936th keyword +937th keyword +938th keyword +939th keyword +940th keyword +941st keyword +942nd keyword +943rd keyword +944th keyword +945th keyword +946th keyword +947th keyword +948th keyword +949th keyword +950th keyword +951st keyword +952nd keyword +953rd keyword +954th keyword +955th keyword +956th keyword +957th keyword +958th keyword +959th keyword +960th keyword +961st keyword +962nd keyword +963rd keyword +964th keyword +965th keyword +966th keyword +967th keyword +968th keyword +969th keyword +970th keyword +971st keyword +972nd keyword +973rd keyword +974th keyword +975th keyword +976th keyword +977th keyword +978th keyword +979th keyword +980th keyword +981st keyword +982nd keyword +983rd keyword +984th keyword +985th keyword +986th keyword +987th keyword +988th keyword +989th keyword +990th keyword +991st keyword +992nd keyword +993rd keyword +994th keyword +995th keyword +996th keyword +997th keyword +998th keyword +999th keyword +1000th keyword +1001st keyword diff --git a/spec/requests/.keep b/spec/requests/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/spec/requests/search_stats_spec.rb b/spec/requests/search_stats_spec.rb new file mode 100644 index 0000000..cc015a9 --- /dev/null +++ b/spec/requests/search_stats_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Search Stats', type: :request do + describe 'POST #create' do + context 'given a valid file' do + it 'inserts keywords to the database and redirects to search stat index' do + user = Fabricate(:user) + login_as_user user + + params = { csv_file: fixture_file_upload('keywords.csv', 'text/csv') } + + post search_stats_path, params: params + + expect(response).to redirect_to search_stats_path + + expect(SearchStat.all.count).to eq(3) + end + end + + context 'given an invalid file data' do + it 'does not insert keywords to the database and raises an error' do + user = Fabricate(:user) + login_as_user user + + params = { csv_file: fixture_file_upload('invalid_data.csv', 'text/csv') } + + expect { post search_stats_path, params: params }.to raise_error(RuntimeError, 'Invalid file data') + + expect(SearchStat.all.count).to eq(0) + end + end + + context 'given an invalid file type' do + it 'does not insert keywords to the database and raises an error' do + user = Fabricate(:user) + login_as_user user + + params = { csv_file: fixture_file_upload('invalid_type.txt', 'text/plain') } + + expect { post search_stats_path, params: params }.to raise_error(RuntimeError, 'Invalid file type') + + expect(SearchStat.all.count).to eq(0) + end + end + + context 'given too many keywords' do + it 'does not insert keywords to the database and raises an error' do + user = Fabricate(:user) + login_as_user user + + params = { csv_file: fixture_file_upload('too_many_keywords.csv', 'text/csv') } + + expect { post search_stats_path, params: params }.to raise_error(RuntimeError, 'Too many keywords') + + expect(SearchStat.all.count).to eq(0) + end + end + end +end diff --git a/spec/support/devise.rb b/spec/support/devise.rb new file mode 100644 index 0000000..f2d43a6 --- /dev/null +++ b/spec/support/devise.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.include Devise::Test::ControllerHelpers, type: :view + config.include Devise::Test::ControllerHelpers, type: :helper + config.include Devise::Test::IntegrationHelpers, type: :feature + config.include Devise::Test::IntegrationHelpers, type: :request +end diff --git a/spec/support/user_authentication.rb b/spec/support/user_authentication.rb new file mode 100644 index 0000000..9f7080d --- /dev/null +++ b/spec/support/user_authentication.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +def login_as_user(user = Fabricate(:user)) + login_as user +end