diff --git a/source/.gitignore b/source/.gitignore index 6a502e9..8f75038 100644 --- a/source/.gitignore +++ b/source/.gitignore @@ -14,3 +14,6 @@ # Ignore all logfiles and tempfiles. /log/*.log /tmp + +# Ignore Vim swapfiles +.*.swp diff --git a/source/.rspec b/source/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/source/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/source/Gemfile b/source/Gemfile index 9627b8b..07d617a 100644 --- a/source/Gemfile +++ b/source/Gemfile @@ -27,7 +27,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'spring', group: :development # Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Use unicorn as the app server # gem 'unicorn' diff --git a/source/Gemfile.lock b/source/Gemfile.lock index fcf8b98..e7b2a20 100644 --- a/source/Gemfile.lock +++ b/source/Gemfile.lock @@ -28,6 +28,7 @@ GEM thread_safe (~> 0.1) tzinfo (~> 1.1) arel (5.0.1.20140414130214) + bcrypt (3.1.10) builder (3.2.2) coffee-rails (4.0.1) coffee-script (>= 2.2.0) @@ -125,6 +126,7 @@ PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1.7) coffee-rails (~> 4.0.0) jbuilder (~> 2.0) jquery-rails @@ -136,3 +138,6 @@ DEPENDENCIES sqlite3 turbolinks uglifier (>= 1.3.0) + +BUNDLED WITH + 1.10.6 diff --git a/source/app/assets/stylesheets/scaffolds.css.scss b/source/app/assets/stylesheets/scaffolds.css.scss new file mode 100644 index 0000000..6ec6a8f --- /dev/null +++ b/source/app/assets/stylesheets/scaffolds.css.scss @@ -0,0 +1,69 @@ +body { + background-color: #fff; + color: #333; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { + color: #000; + &:visited { + color: #666; + } + &:hover { + color: #fff; + background-color: #000; + } +} + +div { + &.field, &.actions { + margin-bottom: 10px; + } +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; + h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; + } + ul li { + font-size: 12px; + list-style: square; + } +} diff --git a/source/app/controllers/application_controller.rb b/source/app/controllers/application_controller.rb index d83690e..d708874 100644 --- a/source/app/controllers/application_controller.rb +++ b/source/app/controllers/application_controller.rb @@ -2,4 +2,6 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + include ApplicationHelper + include SessionsHelper end diff --git a/source/app/controllers/sessions_controller.rb b/source/app/controllers/sessions_controller.rb index 16d11b5..a45daf1 100644 --- a/source/app/controllers/sessions_controller.rb +++ b/source/app/controllers/sessions_controller.rb @@ -1,2 +1,20 @@ class SessionsController < ApplicationController + def new + end + + def create + user = User.find_by(email: params[:session][:email].downcase) + if user && user.authenticate(params[:session][:password]) + log_in user + redirect_to root_url + else + flash[:danger] = 'Invalid email/password combination' + render 'new' + end + end + + def destroy + log_out + redirect_to login_path + end end diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index ef26710..d384ea9 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -1,2 +1,46 @@ class UrlsController < ApplicationController + before_action :logged_in_user, except: [:show] + + # GET /urls + # GET /urls.json + def index + @urls = Url.all + end + + # GET /urls/1 + # GET /urls/1.json + def show + url = Url.find_by!(short_url: params[:id]) + url.click_count += 1 + url.save + redirect_to url.real_url + end + + # GET /urls/new + def new + @url = Url.new + end + + # POST /urls + # POST /urls.json + def create + @url = Url.new(url_params) + + respond_to do |format| + if @url.save + @urls = Url.all + format.html { render :index, notice: 'Url was successfully created.' } + format.json { render :show, status: :created, location: @url } + else + format.html { render :new } + format.json { render json: @url.errors, status: :unprocessable_entity } + end + end + end + + private + # Never trust parameters from the scary internet, only allow the white list through. + def url_params + params.require(:url).permit(:short_url, :real_url) + end end diff --git a/source/app/controllers/users_controller.rb b/source/app/controllers/users_controller.rb index 3e74dea..4696e02 100644 --- a/source/app/controllers/users_controller.rb +++ b/source/app/controllers/users_controller.rb @@ -1,2 +1,76 @@ class UsersController < ApplicationController + before_action :set_user, only: [:show, :edit, :update, :destroy] + before_action :logged_in_user, except: [:create, :new] + + # GET /users + # GET /users.json + def index + @users = User.all + end + + # GET /users/1 + # GET /users/1.json + def show + end + + # GET /users/new + def new + @user = User.new + end + + # GET /users/1/edit + def edit + end + + # POST /users + # POST /users.json + def create + @user = User.new(user_params) + + respond_to do |format| + if @user.save + log_in @user + format.html { redirect_to @user, notice: 'User was successfully created.' } + format.json { render :show, status: :created, location: @user } + else + format.html { render :new } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /users/1 + # PATCH/PUT /users/1.json + def update + respond_to do |format| + if @user.update(user_params) + format.html { redirect_to @user, notice: 'User was successfully updated.' } + format.json { render :show, status: :ok, location: @user } + else + format.html { render :edit } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /users/1 + # DELETE /users/1.json + def destroy + @user.destroy + respond_to do |format| + format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_user + @user = User.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def user_params + params.require(:user).permit(:name, :email, :password, :password_confirmation) + end end diff --git a/source/app/helpers/application_helper.rb b/source/app/helpers/application_helper.rb index de6be79..9d2598f 100644 --- a/source/app/helpers/application_helper.rb +++ b/source/app/helpers/application_helper.rb @@ -1,2 +1,5 @@ module ApplicationHelper + def logged_in_user + redirect_to login_path and return if !logged_in? + end end diff --git a/source/app/helpers/sessions_helper.rb b/source/app/helpers/sessions_helper.rb index 309f8b2..ec23720 100644 --- a/source/app/helpers/sessions_helper.rb +++ b/source/app/helpers/sessions_helper.rb @@ -1,2 +1,18 @@ module SessionsHelper + def log_in(user) + session[:user_id] = user.id + end + + def log_out + session.delete(:user_id) + @current_user = nil + end + + def current_user + @current_user ||= User.find_by(id: session[:user_id]) + end + + def logged_in? + !current_user.nil? + end end diff --git a/source/app/models/url.rb b/source/app/models/url.rb new file mode 100644 index 0000000..9fb3bcd --- /dev/null +++ b/source/app/models/url.rb @@ -0,0 +1,28 @@ +require 'uri' +require 'open-uri' + +class Url < ActiveRecord::Base + before_save :make_short_url + validates :real_url, presence: true + validate :test_url + + private + def make_short_url + self.short_url = SecureRandom.hex(4) if self.short_url.blank? + end + + def test_url + # validate url is http or https + uri = URI(self.real_url) + if self.real_url !~ /\A#{URI::regexp(['http', 'https'])}\z/ + errors.add(:real_url, 'is not http or https') + end + + # validate url is accessible + begin + open(uri.to_s) + rescue + errors.add(:real_url, 'is not accessible') + end + end +end diff --git a/source/app/models/user.rb b/source/app/models/user.rb new file mode 100644 index 0000000..51274de --- /dev/null +++ b/source/app/models/user.rb @@ -0,0 +1,10 @@ +class User < ActiveRecord::Base + before_save { self.email = email.downcase } + validates :name, presence: true, length: { maximum: 50 } + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, length: { maximum: 255 }, + format: { with: VALID_EMAIL_REGEX }, + uniqueness: { case_sensitive: false } + has_secure_password + validates :password, presence: true, length: { minimum: 6 } +end diff --git a/source/app/views/layouts/application.html.erb b/source/app/views/layouts/application.html.erb index f946432..7c7c6d3 100644 --- a/source/app/views/layouts/application.html.erb +++ b/source/app/views/layouts/application.html.erb @@ -7,6 +7,13 @@ <%= csrf_meta_tags %> + <%= yield %> diff --git a/source/app/views/sessions/new.html.erb b/source/app/views/sessions/new.html.erb new file mode 100644 index 0000000..fbcca34 --- /dev/null +++ b/source/app/views/sessions/new.html.erb @@ -0,0 +1,19 @@ +<% provide(:title, "Log in") %> +

Log in

+ +
+
+ <%= form_for(:session, url: login_path) do |f| %> + + <%= f.label :email %> + <%= f.email_field :email, class: 'form-control' %> + + <%= f.label :password %> + <%= f.password_field :password, class: 'form-control' %> + + <%= f.submit "Log in", class: "btn btn-primary" %> + <% end %> + +

New user? <%= link_to "Sign up now!", signup_path %>

+
+
diff --git a/source/app/views/urls/_form.html.erb b/source/app/views/urls/_form.html.erb new file mode 100644 index 0000000..d77bb79 --- /dev/null +++ b/source/app/views/urls/_form.html.erb @@ -0,0 +1,21 @@ +<%= form_for(@url) do |f| %> + <% if @url.errors.any? %> +
+

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

+ + +
+ <% end %> + +
+ <%= f.label :real_url %>
+ <%= f.text_field :real_url %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/source/app/views/urls/edit.html.erb b/source/app/views/urls/edit.html.erb new file mode 100644 index 0000000..e52fdf4 --- /dev/null +++ b/source/app/views/urls/edit.html.erb @@ -0,0 +1,6 @@ +

Editing url

+ +<%= render 'form' %> + +<%= link_to 'Show', @url %> | +<%= link_to 'Back', urls_path %> diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb new file mode 100644 index 0000000..996afa6 --- /dev/null +++ b/source/app/views/urls/index.html.erb @@ -0,0 +1,27 @@ +

Listing urls

+ + + + + + + + + + + + + <% @urls.each do |url| %> + + + + + + + <% end %> + +
Short urlReal urlClicks
<%= url.short_url %><%= url.real_url %><%= url.click_count %><%= link_to 'Go to', url_path(url.short_url) %>
+ +
+ +<%= link_to 'New Url', new_url_path %> diff --git a/source/app/views/urls/index.json.jbuilder b/source/app/views/urls/index.json.jbuilder new file mode 100644 index 0000000..6736639 --- /dev/null +++ b/source/app/views/urls/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@urls) do |url| + json.extract! url, :id, :short_url, :real_url + json.url url_url(url, format: :json) +end diff --git a/source/app/views/urls/new.html.erb b/source/app/views/urls/new.html.erb new file mode 100644 index 0000000..7e711d5 --- /dev/null +++ b/source/app/views/urls/new.html.erb @@ -0,0 +1,5 @@ +

New url

+ +<%= render 'form' %> + +<%= link_to 'Back', urls_path %> diff --git a/source/app/views/urls/show.html.erb b/source/app/views/urls/show.html.erb new file mode 100644 index 0000000..aa1e122 --- /dev/null +++ b/source/app/views/urls/show.html.erb @@ -0,0 +1,13 @@ +

<%= notice %>

+ +

+ Short url: + <%= @url.short_url %> +

+ +

+ Real url: + <%= @url.real_url %> +

+ +<%= link_to 'Back', urls_path %> diff --git a/source/app/views/urls/show.json.jbuilder b/source/app/views/urls/show.json.jbuilder new file mode 100644 index 0000000..5c060bf --- /dev/null +++ b/source/app/views/urls/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @url, :id, :short_url, :real_url, :created_at, :updated_at diff --git a/source/app/views/users/_form.html.erb b/source/app/views/users/_form.html.erb new file mode 100644 index 0000000..efe5fcc --- /dev/null +++ b/source/app/views/users/_form.html.erb @@ -0,0 +1,33 @@ +<%= form_for(@user) do |f| %> + <% if @user.errors.any? %> +
+

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

+ + +
+ <% end %> + +
+ <%= f.label :name %>
+ <%= f.text_field :name %> +
+
+ <%= f.label :email %>
+ <%= f.text_field :email %> +
+
+ <%= f.label :password %>
+ <%= f.password_field :password %> +
+
+ <%= f.label :password_confirmation, "Confirmation" %>
+ <%= f.password_field :password_confirmation %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/source/app/views/users/edit.html.erb b/source/app/views/users/edit.html.erb new file mode 100644 index 0000000..99bd4cc --- /dev/null +++ b/source/app/views/users/edit.html.erb @@ -0,0 +1,6 @@ +

Editing user

+ +<%= render 'form' %> + +<%= link_to 'Show', @user %> | +<%= link_to 'Back', users_path %> diff --git a/source/app/views/users/index.html.erb b/source/app/views/users/index.html.erb new file mode 100644 index 0000000..6041d5e --- /dev/null +++ b/source/app/views/users/index.html.erb @@ -0,0 +1,27 @@ +

Listing users

+ + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + + <% end %> + +
NameEmail
<%= user.name %><%= user.email %><%= link_to 'Show', user %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New User', new_user_path %> diff --git a/source/app/views/users/index.json.jbuilder b/source/app/views/users/index.json.jbuilder new file mode 100644 index 0000000..2972532 --- /dev/null +++ b/source/app/views/users/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@users) do |user| + json.extract! user, :id, :name, :email + json.url user_url(user, format: :json) +end diff --git a/source/app/views/users/new.html.erb b/source/app/views/users/new.html.erb new file mode 100644 index 0000000..efc0404 --- /dev/null +++ b/source/app/views/users/new.html.erb @@ -0,0 +1,5 @@ +

New user

+ +<%= render 'form' %> + +<%= link_to 'Back', users_path %> diff --git a/source/app/views/users/show.html.erb b/source/app/views/users/show.html.erb new file mode 100644 index 0000000..3816c64 --- /dev/null +++ b/source/app/views/users/show.html.erb @@ -0,0 +1,14 @@ +

<%= notice %>

+ +

+ Name: + <%= @user.name %> +

+ +

+ Email: + <%= @user.email %> +

+ +<%= link_to 'Edit', edit_user_path(@user) %> | +<%= link_to 'Back', users_path %> diff --git a/source/app/views/users/show.json.jbuilder b/source/app/views/users/show.json.jbuilder new file mode 100644 index 0000000..53f1bb9 --- /dev/null +++ b/source/app/views/users/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @user, :id, :name, :email, :created_at, :updated_at diff --git a/source/config/routes.rb b/source/config/routes.rb index 3f66539..e6edb40 100644 --- a/source/config/routes.rb +++ b/source/config/routes.rb @@ -1,4 +1,15 @@ Rails.application.routes.draw do + get 'login' => 'sessions#new' + post 'login' => 'sessions#create' + delete 'logout' => 'sessions#destroy' + get 'signup' => 'users#new' + + resources :users + + resources :urls, only: [:index, :new, :create, :show] + + root 'urls#index' + # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/source/db/migrate/20151020143000_create_urls.rb b/source/db/migrate/20151020143000_create_urls.rb new file mode 100644 index 0000000..0e88fb5 --- /dev/null +++ b/source/db/migrate/20151020143000_create_urls.rb @@ -0,0 +1,10 @@ +class CreateUrls < ActiveRecord::Migration + def change + create_table :urls do |t| + t.string :short_url + t.string :real_url + + t.timestamps + end + end +end diff --git a/source/db/migrate/20151020155111_add_click_count_to_urls.rb b/source/db/migrate/20151020155111_add_click_count_to_urls.rb new file mode 100644 index 0000000..93b0e1f --- /dev/null +++ b/source/db/migrate/20151020155111_add_click_count_to_urls.rb @@ -0,0 +1,5 @@ +class AddClickCountToUrls < ActiveRecord::Migration + def change + add_column :urls, :click_count, :integer, default: 0 + end +end diff --git a/source/db/migrate/20151021135657_create_users.rb b/source/db/migrate/20151021135657_create_users.rb new file mode 100644 index 0000000..7e0dcd9 --- /dev/null +++ b/source/db/migrate/20151021135657_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/source/db/migrate/20151021140221_add_password_to_users.rb b/source/db/migrate/20151021140221_add_password_to_users.rb new file mode 100644 index 0000000..7b370a5 --- /dev/null +++ b/source/db/migrate/20151021140221_add_password_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordToUsers < ActiveRecord::Migration + def change + add_column :users, :password_digest, :string + end +end diff --git a/source/db/schema.rb b/source/db/schema.rb new file mode 100644 index 0000000..431986d --- /dev/null +++ b/source/db/schema.rb @@ -0,0 +1,32 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20151021140221) do + + create_table "urls", force: true do |t| + t.string "short_url" + t.string "real_url" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "click_count", default: 0 + end + + create_table "users", force: true do |t| + t.string "name" + t.string "email" + t.datetime "created_at" + t.datetime "updated_at" + t.string "password_digest" + end + +end diff --git a/source/db/seeds.rb b/source/db/seeds.rb index 4edb1e8..b036022 100644 --- a/source/db/seeds.rb +++ b/source/db/seeds.rb @@ -5,3 +5,5 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) +user = User.create({ name: 'Ima Tester', email: 'admin@example.com', password: 'tester' }) +user.save diff --git a/source/spec/controllers/sessions_controller_spec.rb b/source/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 0000000..be9f566 --- /dev/null +++ b/source/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe SessionsController, :type => :controller do + + describe "GET new" do + it "returns http success" do + get :new + expect(response).to have_http_status(:success) + end + end + +end diff --git a/source/spec/controllers/urls_controller_spec.rb b/source/spec/controllers/urls_controller_spec.rb new file mode 100644 index 0000000..df4ac05 --- /dev/null +++ b/source/spec/controllers/urls_controller_spec.rb @@ -0,0 +1,123 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +RSpec.describe UrlsController, :type => :controller do + + # This should return the minimal set of attributes required to create a valid + # Url. As you add validations to Url, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + { short_url: 'c35c02c2', real_url: 'http://www.google.com', click_count: 2 } + } + + let(:invalid_attributes) { + { short_url: 'c35c02c2', real_url: 'httpq://www.google.com', click_count: -1 } + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # UrlsController. Be sure to keep this updated too. + let(:valid_session) { { user_id: 1 } } + include SessionsHelper + + describe "GET index" do + it "redirects to login page if not logged in" do + get :index + expect(response).to redirect_to(login_path) + end + + it "assigns all urls as @urls" do + url = Url.create! valid_attributes + get :index, {}, valid_session + expect(assigns(:urls)).to eq([url]) + end + end + + describe "GET show" do + it "responds 404 to invalid short_url" do + expect { + get :show, {:id => 'invalid short url'}, valid_session + }.to raise_exception(ActiveRecord::RecordNotFound) + end + it "redirects to the real_url" do + url = Url.create! valid_attributes + get :show, {:id => url.short_url}, valid_session + expect(response).to redirect_to(url.real_url) + end + it "increments click_count" do + url = Url.create! valid_attributes + old_count = url.click_count + get :show, {:id => url.short_url}, valid_session + url.reload + expect(url.click_count).to be > old_count + end + end + + describe "GET new" do + it "redirects to login page if not logged in" do + get :new + expect(response).to redirect_to(login_path) + end + + it "assigns a new url as @url" do + get :new, {}, valid_session + expect(assigns(:url)).to be_a_new(Url) + end + end + + describe "POST create" do + it "redirects to login page if not logged in" do + post :create + expect(response).to redirect_to(login_path) + end + + describe "with valid params" do + it "creates a new Url" do + expect { + post :create, {:url => valid_attributes}, valid_session + }.to change(Url, :count).by(1) + end + + it "assigns a newly created url as @url" do + post :create, {:url => valid_attributes}, valid_session + expect(assigns(:url)).to be_a(Url) + expect(assigns(:url)).to be_persisted + end + + it "redirects to the index url" do + post :create, {:url => valid_attributes}, valid_session + expect(response).to render_template("index") + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved url as @url" do + post :create, {:url => invalid_attributes}, valid_session + expect(assigns(:url)).to be_a_new(Url) + end + + it "re-renders the 'new' template" do + post :create, {:url => invalid_attributes}, valid_session + expect(response).to render_template("new") + end + end + end + +end diff --git a/source/spec/controllers/users_controller_spec.rb b/source/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..0a2f24c --- /dev/null +++ b/source/spec/controllers/users_controller_spec.rb @@ -0,0 +1,185 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +RSpec.describe UsersController, :type => :controller do + + # This should return the minimal set of attributes required to create a valid + # User. As you add validations to User, be sure to + # adjust the attributes here as well. + let(:valid_attributes) { + { name: 'Ima Tester', email: 'tester@example.com', password: 'tester' } + } + + let(:invalid_attributes) { + { name: 'Ima Tester', email: 'bademail', password: 'tester' } + } + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # UsersController. Be sure to keep this updated too. + let(:valid_session) { { user_id: 1 } } + include SessionsHelper + + describe "GET index" do + it "redirects to login page if not logged in" do + get :index + expect(response).to redirect_to(login_path) + end + + it "assigns all users as @users" do + users = User.all + get :index, {}, valid_session + expect(assigns(:users)).to eq(users) + end + end + + describe "GET show" do + it "redirects to login page if not logged in" do + user = User.create! valid_attributes + get :show, {:id => user.to_param} + expect(response).to redirect_to(login_path) + end + + it "assigns the requested user as @user" do + user = User.create! valid_attributes + get :show, {:id => user.to_param}, valid_session + expect(assigns(:user)).to eq(user) + end + end + + describe "GET new" do + it "assigns a new user as @user if not logged in" do + get :new, {} + expect(assigns(:user)).to be_a_new(User) + end + it "assigns a new user as @user" do + get :new, {}, valid_session + expect(assigns(:user)).to be_a_new(User) + end + end + + describe "GET edit" do + it "redirects to login page if not logged in" do + user = User.create! valid_attributes + get :edit, {:id => user.to_param} + expect(response).to redirect_to(login_path) + end + + it "assigns the requested user as @user" do + user = User.create! valid_attributes + get :edit, {:id => user.to_param}, valid_session + expect(assigns(:user)).to eq(user) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new User" do + expect { + post :create, {:user => valid_attributes}, valid_session + }.to change(User, :count).by(1) + end + + it "assigns a newly created user as @user" do + post :create, {:user => valid_attributes}, valid_session + expect(assigns(:user)).to be_a(User) + expect(assigns(:user)).to be_persisted + end + + it "redirects to the created user" do + post :create, {:user => valid_attributes}, valid_session + expect(response).to redirect_to(User.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved user as @user" do + post :create, {:user => invalid_attributes}, valid_session + expect(assigns(:user)).to be_a_new(User) + end + + it "re-renders the 'new' template" do + post :create, {:user => invalid_attributes}, valid_session + expect(response).to render_template("new") + end + end + end + + describe "PUT update" do + let(:new_attributes) { + { name: 'Ima C. Tester', email: 'tester2@example.com', password: 'tester' } + } + + it "redirects to login page if not logged in" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => new_attributes} + expect(response).to redirect_to(login_path) + end + describe "with valid params" do + it "updates the requested user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => new_attributes}, valid_session + expect(user.name).to eq(valid_attributes[:name]) + end + + it "assigns the requested user as @user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => valid_attributes}, valid_session + expect(assigns(:user)).to eq(user) + end + + it "redirects to the user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => valid_attributes}, valid_session + expect(response).to redirect_to(user) + end + end + + describe "with invalid params" do + it "assigns the user as @user" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => invalid_attributes}, valid_session + expect(assigns(:user)).to eq(user) + end + + it "re-renders the 'edit' template" do + user = User.create! valid_attributes + put :update, {:id => user.to_param, :user => invalid_attributes}, valid_session + expect(response).to render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested user" do + user = User.create! valid_attributes + expect { + delete :destroy, {:id => user.to_param}, valid_session + }.to change(User, :count).by(-1) + end + + it "redirects to the users list" do + user = User.create! valid_attributes + delete :destroy, {:id => user.to_param}, valid_session + expect(response).to redirect_to(users_url) + end + end + +end diff --git a/source/spec/helpers/sessions_helper_spec.rb b/source/spec/helpers/sessions_helper_spec.rb new file mode 100644 index 0000000..08f15a7 --- /dev/null +++ b/source/spec/helpers/sessions_helper_spec.rb @@ -0,0 +1,55 @@ +require 'rails_helper' +include SessionsHelper + +# Specs in this file have access to a helper object that includes +# the SessionsHelper. For example: +# +# describe SessionsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe SessionsHelper, :type => :helper do + let(:valid_attributes) { + { name: 'Ima Tester', email: 'tester@example.com', password: 'tester' } + } + + describe "log_in" do + it "log in as invalid user" do + log_in(User.create) + expect(session[:user_id]).to be_nil + end + + it "log in as valid user" do + user = User.create! valid_attributes + log_in(user) + expect(session[:user_id]).not_to be_nil + end + end + + describe "log_out" do + it "log out of account" do + log_out + expect(session[:user_id]).to be_nil + end + end + + describe "current_user" do + it "get current user" do + session[:user_id] = 1 + expect(current_user).not_to be_nil + end + end + + describe "logged in or not" do + it "logged_in? while logged in" do + session[:user_id] = 1 + expect(logged_in?).to be true + end + it "logged_in? while NOT logged in" do + expect(logged_in?).to be false + end + end +end diff --git a/source/spec/helpers/urls_helper_spec.rb b/source/spec/helpers/urls_helper_spec.rb new file mode 100644 index 0000000..d08d44f --- /dev/null +++ b/source/spec/helpers/urls_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the UrlsHelper. For example: +# +# describe UrlsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe UrlsHelper, :type => :helper do + #pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/helpers/users_helper_spec.rb b/source/spec/helpers/users_helper_spec.rb new file mode 100644 index 0000000..2b0ba96 --- /dev/null +++ b/source/spec/helpers/users_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the UsersHelper. For example: +# +# describe UsersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe UsersHelper, :type => :helper do + #pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/models/url_spec.rb b/source/spec/models/url_spec.rb new file mode 100644 index 0000000..a326609 --- /dev/null +++ b/source/spec/models/url_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe Url, :type => :model do + let(:valid_attributes) { + { short_url: 'c35c02c2', real_url: 'http://www.google.com', click_count: 2 } + } + + let(:valid_attributes_no_short_url) { + { short_url: '', real_url: 'http://www.google.com', click_count: 2 } + } + + it "assigns a short_url if none exists upon save" do + url = Url.create! valid_attributes_no_short_url + expect(url.short_url).not_to be_empty + end +end diff --git a/source/spec/models/user_spec.rb b/source/spec/models/user_spec.rb new file mode 100644 index 0000000..27d6283 --- /dev/null +++ b/source/spec/models/user_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe User, :type => :model do + #pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/rails_helper.rb b/source/spec/rails_helper.rb new file mode 100644 index 0000000..e6c0b68 --- /dev/null +++ b/source/spec/rails_helper.rb @@ -0,0 +1,50 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV["RAILS_ENV"] ||= 'test' +require 'spec_helper' +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } + +# Checks for pending migrations before tests are run. +# If you are not using ActiveRecord, you can remove this line. +ActiveRecord::Migration.maintain_test_schema! + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! +end diff --git a/source/spec/requests/urls_spec.rb b/source/spec/requests/urls_spec.rb new file mode 100644 index 0000000..bc3a4fc --- /dev/null +++ b/source/spec/requests/urls_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe "Urls", :type => :request do + #describe "GET /urls" do + # it "works! (now write some real specs)" do + # get urls_path + # expect(response).to have_http_status(200) + # end + #end +end diff --git a/source/spec/requests/users_spec.rb b/source/spec/requests/users_spec.rb new file mode 100644 index 0000000..2c9b27e --- /dev/null +++ b/source/spec/requests/users_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe "Users", :type => :request do + #describe "GET /users" do + # it "works! (now write some real specs)" do + # get users_path + # expect(response).to have_http_status(200) + # end + #end +end diff --git a/source/spec/routing/urls_routing_spec.rb b/source/spec/routing/urls_routing_spec.rb new file mode 100644 index 0000000..5d62e0f --- /dev/null +++ b/source/spec/routing/urls_routing_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +RSpec.describe UrlsController, :type => :routing do + describe "routing" do + + it "routes to #index" do + expect(:get => "/urls").to route_to("urls#index") + end + + it "routes to #new" do + expect(:get => "/urls/new").to route_to("urls#new") + end + + it "routes to #show" do + expect(:get => "/urls/1").to route_to("urls#show", :id => "1") + end + + it "routes to #create" do + expect(:post => "/urls").to route_to("urls#create") + end + + end +end diff --git a/source/spec/routing/users_routing_spec.rb b/source/spec/routing/users_routing_spec.rb new file mode 100644 index 0000000..751ad75 --- /dev/null +++ b/source/spec/routing/users_routing_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +RSpec.describe UsersController, :type => :routing do + describe "routing" do + + it "routes to #index" do + expect(:get => "/users").to route_to("users#index") + end + + it "routes to #new" do + expect(:get => "/users/new").to route_to("users#new") + end + + it "routes to #show" do + expect(:get => "/users/1").to route_to("users#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/users/1/edit").to route_to("users#edit", :id => "1") + end + + it "routes to #create" do + expect(:post => "/users").to route_to("users#create") + end + + it "routes to #update" do + expect(:put => "/users/1").to route_to("users#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/users/1").to route_to("users#destroy", :id => "1") + end + + end +end diff --git a/source/spec/spec_helper.rb b/source/spec/spec_helper.rb new file mode 100644 index 0000000..275ba49 --- /dev/null +++ b/source/spec/spec_helper.rb @@ -0,0 +1,85 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause this +# file to always be loaded, without a need to explicitly require it in any files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need it. +# +# The `.rspec` file also contains a few flags that are not defaults but that +# users commonly want. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Limits the available syntax to the non-monkey patched syntax that is recommended. + # For more details, see: + # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax + # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/source/spec/views/sessions/new.html.erb_spec.rb b/source/spec/views/sessions/new.html.erb_spec.rb new file mode 100644 index 0000000..bc53370 --- /dev/null +++ b/source/spec/views/sessions/new.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "sessions/new.html.erb", :type => :view do + #pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/views/urls/edit.html.erb_spec.rb b/source/spec/views/urls/edit.html.erb_spec.rb new file mode 100644 index 0000000..fb4b02b --- /dev/null +++ b/source/spec/views/urls/edit.html.erb_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe "urls/edit", :type => :view do + before(:each) do + @url = assign(:url, Url.create!( + :real_url => "http://example.com" + )) + end + + it "renders the edit url form" do + render + + assert_select "form[action=?][method=?]", url_path(@url), "post" do + assert_select "input#url_real_url[name=?]", "url[real_url]" + end + end +end diff --git a/source/spec/views/urls/index.html.erb_spec.rb b/source/spec/views/urls/index.html.erb_spec.rb new file mode 100644 index 0000000..a6f48c4 --- /dev/null +++ b/source/spec/views/urls/index.html.erb_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe "urls/index", :type => :view do + before(:each) do + assign(:urls, [ + Url.create!( + :short_url => "Short Url", + :real_url => "http://example.com" + ), + Url.create!( + :short_url => "Short Url", + :real_url => "https://google.com" + ) + ]) + end + + it "renders a list of urls" do + render + assert_select "tr>td", :text => "Short Url".to_s + assert_select "tr>td", :text => "http://example.com".to_s + end +end diff --git a/source/spec/views/urls/new.html.erb_spec.rb b/source/spec/views/urls/new.html.erb_spec.rb new file mode 100644 index 0000000..7096508 --- /dev/null +++ b/source/spec/views/urls/new.html.erb_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe "urls/new", :type => :view do + before(:each) do + assign(:url, Url.new( + :real_url => "MyString" + )) + end + + it "renders new url form" do + render + + assert_select "form[action=?][method=?]", urls_path, "post" do + assert_select "input#url_real_url[name=?]", "url[real_url]" + end + end +end diff --git a/source/spec/views/urls/show.html.erb_spec.rb b/source/spec/views/urls/show.html.erb_spec.rb new file mode 100644 index 0000000..b83f902 --- /dev/null +++ b/source/spec/views/urls/show.html.erb_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe "urls/show", :type => :view do + before(:each) do + @url = assign(:url, Url.create!( + :short_url => "Short Url", + :real_url => "http://example.com" + )) + end + + it "renders attributes in

" do + render + expect(rendered).to match(/Short Url/) + expect(rendered).to match(/http:\/\/example.com/) + end +end diff --git a/source/spec/views/users/edit.html.erb_spec.rb b/source/spec/views/users/edit.html.erb_spec.rb new file mode 100644 index 0000000..d69374e --- /dev/null +++ b/source/spec/views/users/edit.html.erb_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe "users/edit", :type => :view do + before(:each) do + @user = assign(:user, User.create!( + :name => "MyString", + :email => "test@example.com", + :password => "123456" + )) + end + + it "renders the edit user form" do + render + + assert_select "form[action=?][method=?]", user_path(@user), "post" do + + assert_select "input#user_name[name=?]", "user[name]" + + assert_select "input#user_email[name=?]", "user[email]" + end + end +end diff --git a/source/spec/views/users/index.html.erb_spec.rb b/source/spec/views/users/index.html.erb_spec.rb new file mode 100644 index 0000000..1300a46 --- /dev/null +++ b/source/spec/views/users/index.html.erb_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +RSpec.describe "users/index", :type => :view do + before(:each) do + assign(:users, [ + User.create!( + :name => "Name", + :email => "test@example.com", + :password => "password" + ), + User.create!( + :name => "Name", + :email => "test2@example.com", + :password => "password" + ) + ]) + end + + it "renders a list of users" do + render + assert_select "tr>td", :text => "Name".to_s, :count => 2 + assert_select "tr>td", :text => "test@example.com".to_s, :count => 1 + assert_select "tr>td", :text => "test2@example.com".to_s, :count => 1 + end +end diff --git a/source/spec/views/users/new.html.erb_spec.rb b/source/spec/views/users/new.html.erb_spec.rb new file mode 100644 index 0000000..3ec4867 --- /dev/null +++ b/source/spec/views/users/new.html.erb_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +RSpec.describe "users/new", :type => :view do + before(:each) do + assign(:user, User.new( + :name => "MyString", + :email => "admin@example.com", + :password => "test" + )) + end + + it "renders new user form" do + render + + assert_select "form[action=?][method=?]", users_path, "post" do + + assert_select "input#user_name[name=?]", "user[name]" + + assert_select "input#user_email[name=?]", "user[email]" + end + end +end diff --git a/source/spec/views/users/show.html.erb_spec.rb b/source/spec/views/users/show.html.erb_spec.rb new file mode 100644 index 0000000..bd71359 --- /dev/null +++ b/source/spec/views/users/show.html.erb_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe "users/show", :type => :view do + before(:each) do + @user = assign(:user, User.create!( + :name => "Name", + :email => "test@example.com", + :password => "tester" + )) + end + + it "renders attributes in

" do + render + expect(rendered).to match(/Name/) + expect(rendered).to match(/test@example.com/) + end +end