+ <%= render partial: "review_form", locals: {button: "Submit review"} %>
+
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 0000000000..66e9889e8b
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/bin/rails b/bin/rails
new file mode 100755
index 0000000000..fb5757471e
--- /dev/null
+++ b/bin/rails
@@ -0,0 +1,15 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+APP_PATH = File.expand_path('../config/application', __dir__)
+require_relative '../config/boot'
+require 'rails/commands'
+
+if ENV['RAILS_ENV'] == 'test'
+ require 'simplecov'
+ SimpleCov.start 'rails'
+ puts "required simplecov"
+end
diff --git a/bin/rake b/bin/rake
new file mode 100755
index 0000000000..d87d5f5781
--- /dev/null
+++ b/bin/rake
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/setup b/bin/setup
new file mode 100755
index 0000000000..78c4e861dc
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,38 @@
+#!/usr/bin/env ruby
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
+ # end
+
+ puts "\n== Preparing database =="
+ system! 'bin/rails db:setup'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/bin/spring b/bin/spring
new file mode 100755
index 0000000000..fb2ec2ebb4
--- /dev/null
+++ b/bin/spring
@@ -0,0 +1,17 @@
+#!/usr/bin/env ruby
+
+# This file loads spring without using Bundler, in order to be fast.
+# It gets overwritten when you run the `spring binstub` command.
+
+unless defined?(Spring)
+ require 'rubygems'
+ require 'bundler'
+
+ lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
+ spring = lockfile.specs.detect { |spec| spec.name == "spring" }
+ if spring
+ Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
+ gem 'spring', spring.version
+ require 'spring/binstub'
+ end
+end
diff --git a/bin/update b/bin/update
new file mode 100755
index 0000000000..a8e4462f20
--- /dev/null
+++ b/bin/update
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a way to update your development environment automatically.
+ # Add necessary update steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ puts "\n== Updating database =="
+ system! 'bin/rails db:migrate'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/bin/yarn b/bin/yarn
new file mode 100755
index 0000000000..c2bacef836
--- /dev/null
+++ b/bin/yarn
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+VENDOR_PATH = File.expand_path('..', __dir__)
+Dir.chdir(VENDOR_PATH) do
+ begin
+ exec "yarnpkg #{ARGV.join(" ")}"
+ rescue Errno::ENOENT
+ $stderr.puts "Yarn executable was not detected in the system."
+ $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+ exit 1
+ end
+end
diff --git a/config.ru b/config.ru
new file mode 100644
index 0000000000..f7ba0b527b
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,5 @@
+# This file is used by Rack-based servers to start the application.
+
+require_relative 'config/environment'
+
+run Rails.application
diff --git a/config/application.rb b/config/application.rb
new file mode 100644
index 0000000000..b1dc090fe1
--- /dev/null
+++ b/config/application.rb
@@ -0,0 +1,26 @@
+require_relative 'boot'
+
+require 'rails/all'
+
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(*Rails.groups)
+
+module Betsy
+ class Application < Rails::Application
+ config.generators do |g|
+ # Force new test files to be generated in the minitest-spec style
+ g.test_framework :minitest, spec: true
+
+ # Always use .js files, never .coffee
+ g.javascript_engine :js
+ end
+ # Initialize configuration defaults for originally generated Rails version.
+ config.load_defaults 5.1
+
+ config.serve_static_assets = true
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+ end
+end
diff --git a/config/boot.rb b/config/boot.rb
new file mode 100644
index 0000000000..30f5120df6
--- /dev/null
+++ b/config/boot.rb
@@ -0,0 +1,3 @@
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+
+require 'bundler/setup' # Set up gems listed in the Gemfile.
diff --git a/config/cable.yml b/config/cable.yml
new file mode 100644
index 0000000000..3cba994bb2
--- /dev/null
+++ b/config/cable.yml
@@ -0,0 +1,10 @@
+development:
+ adapter: async
+
+test:
+ adapter: async
+
+production:
+ adapter: redis
+ url: redis://localhost:6379/1
+ channel_prefix: betsy_production
diff --git a/config/database.yml b/config/database.yml
new file mode 100644
index 0000000000..6903bb6083
--- /dev/null
+++ b/config/database.yml
@@ -0,0 +1,85 @@
+# PostgreSQL. Versions 9.1 and up are supported.
+#
+# Install the pg driver:
+# gem install pg
+# On OS X with Homebrew:
+# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
+# On OS X with MacPorts:
+# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
+# On Windows:
+# gem install pg
+# Choose the win32 build.
+# Install PostgreSQL and put its /bin directory on your path.
+#
+# Configure Using Gemfile
+# gem 'pg'
+#
+default: &default
+ adapter: postgresql
+ encoding: unicode
+ # For details on connection pooling, see Rails configuration guide
+ # http://guides.rubyonrails.org/configuring.html#database-pooling
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+
+development:
+ <<: *default
+ database: betsy_development
+
+ # The specified database role being used to connect to postgres.
+ # To create additional roles in postgres see `$ createuser --help`.
+ # When left blank, postgres will use the default role. This is
+ # the same name as the operating system user that initialized the database.
+ #username: betsy
+
+ # The password associated with the postgres role (username).
+ #password:
+
+ # Connect on a TCP socket. Omitted by default since the client uses a
+ # domain socket that doesn't need configuration. Windows does not have
+ # domain sockets, so uncomment these lines.
+ #host: localhost
+
+ # The TCP port the server listens on. Defaults to 5432.
+ # If your server runs on a different port number, change accordingly.
+ #port: 5432
+
+ # Schema search path. The server defaults to $user,public
+ #schema_search_path: myapp,sharedapp,public
+
+ # Minimum log levels, in increasing order:
+ # debug5, debug4, debug3, debug2, debug1,
+ # log, notice, warning, error, fatal, and panic
+ # Defaults to warning.
+ #min_messages: notice
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ <<: *default
+ database: betsy_test
+
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%= ENV['DATABASE_URL'] %>
+#
+production:
+ <<: *default
+ database: betsy_production
+ username: betsy
+ password: <%= ENV['BETSY_DATABASE_PASSWORD'] %>
diff --git a/config/environment.rb b/config/environment.rb
new file mode 100644
index 0000000000..426333bb46
--- /dev/null
+++ b/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the Rails application.
+require_relative 'application'
+
+# Initialize the Rails application.
+Rails.application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
new file mode 100644
index 0000000000..5187e22186
--- /dev/null
+++ b/config/environments/development.rb
@@ -0,0 +1,54 @@
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Do not eager load code on boot.
+ config.eager_load = false
+
+ # Show full error reports.
+ config.consider_all_requests_local = true
+
+ # Enable/disable caching. By default caching is disabled.
+ if Rails.root.join('tmp/caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+
+ config.cache_store = :memory_store
+ config.public_file_server.headers = {
+ 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
+ }
+ else
+ config.action_controller.perform_caching = false
+
+ config.cache_store = :null_store
+ end
+
+ # Don't care if the mailer can't send.
+ config.action_mailer.raise_delivery_errors = false
+
+ config.action_mailer.perform_caching = false
+
+ # Print deprecation notices to the Rails logger.
+ config.active_support.deprecation = :log
+
+ # Raise an error on page load if there are pending migrations.
+ config.active_record.migration_error = :page_load
+
+ # Debug mode disables concatenation and preprocessing of assets.
+ # This option may cause significant delays in view rendering with a large
+ # number of complex assets.
+ config.assets.debug = true
+
+ # Suppress logger output for asset requests.
+ config.assets.quiet = true
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+
+ # Use an evented file watcher to asynchronously detect changes in source code,
+ # routes, locales, etc. This feature depends on the listen gem.
+ config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+end
diff --git a/config/environments/production.rb b/config/environments/production.rb
new file mode 100644
index 0000000000..9284f84839
--- /dev/null
+++ b/config/environments/production.rb
@@ -0,0 +1,91 @@
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # Code is not reloaded between requests.
+ config.cache_classes = true
+
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both threaded web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
+ config.eager_load = true
+
+ # Full error reports are disabled and caching is turned on.
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
+ # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
+ # `config/secrets.yml.key`.
+ config.read_encrypted_secrets = true
+
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+
+ # Compress JavaScripts and CSS.
+ config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
+
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
+ config.assets.compile = false
+
+ # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
+ # Specifies the header that your server uses for sending files.
+ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
+
+ # Mount Action Cable outside main process or domain
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = 'wss://example.com/cable'
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # Use the lowest log level to ensure availability of diagnostic information
+ # when problems arise.
+ config.log_level = :debug
+
+ # Prepend all log lines with the following tags.
+ config.log_tags = [ :request_id ]
+
+ # Use a different cache store in production.
+ # config.cache_store = :mem_cache_store
+
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "betsy_#{Rails.env}"
+ config.action_mailer.perform_caching = false
+
+ # Ignore bad email addresses and do not raise email delivery errors.
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation cannot be found).
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners.
+ config.active_support.deprecation = :notify
+
+ # Use default logging formatter so that PID and timestamp are not suppressed.
+ config.log_formatter = ::Logger::Formatter.new
+
+ # Use a different logger for distributed setups.
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
+
+ if ENV["RAILS_LOG_TO_STDOUT"].present?
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ end
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+end
diff --git a/config/environments/test.rb b/config/environments/test.rb
new file mode 100644
index 0000000000..8e5cbde533
--- /dev/null
+++ b/config/environments/test.rb
@@ -0,0 +1,42 @@
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Do not eager load code on boot. This avoids loading your whole application
+ # just for the purpose of running a single test. If you are using a tool that
+ # preloads Rails for running tests, you may have to set it to true.
+ config.eager_load = false
+
+ # Configure public file server for tests with Cache-Control for performance.
+ config.public_file_server.enabled = true
+ config.public_file_server.headers = {
+ 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
+ }
+
+ # Show full error reports and disable caching.
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates.
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
+ config.action_mailer.perform_caching = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Print deprecation notices to the stderr.
+ config.active_support.deprecation = :stderr
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+end
diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb
new file mode 100644
index 0000000000..89d2efab2b
--- /dev/null
+++ b/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+# ActiveSupport::Reloader.to_prepare do
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
+# end
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
new file mode 100644
index 0000000000..4b828e80cb
--- /dev/null
+++ b/config/initializers/assets.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Add additional assets to the asset load path.
+# Rails.application.config.assets.paths << Emoji.images_path
+# Add Yarn node_modules folder to the asset load path.
+Rails.application.config.assets.paths << Rails.root.join('node_modules')
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in the app/assets
+# folder are already added.
+# Rails.application.config.assets.precompile += %w( admin.js admin.css )
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
new file mode 100644
index 0000000000..59385cdf37
--- /dev/null
+++ b/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb
new file mode 100644
index 0000000000..5a6a32d371
--- /dev/null
+++ b/config/initializers/cookies_serializer.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Specify a serializer for the signed and encrypted cookie jars.
+# Valid options are :json, :marshal, and :hybrid.
+Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 0000000000..4a994e1e7b
--- /dev/null
+++ b/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Configure sensitive parameters which will be filtered from the log file.
+Rails.application.config.filter_parameters += [:password]
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
new file mode 100644
index 0000000000..ac033bf9dc
--- /dev/null
+++ b/config/initializers/inflections.rb
@@ -0,0 +1,16 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format. Inflections
+# are locale specific, and you may define rules for as many different
+# locales as you wish. All of these examples are active by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
+
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
new file mode 100644
index 0000000000..dc1899682b
--- /dev/null
+++ b/config/initializers/mime_types.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
new file mode 100644
index 0000000000..fd4416122a
--- /dev/null
+++ b/config/initializers/omniauth.rb
@@ -0,0 +1,3 @@
+Rails.application.config.middleware.use OmniAuth::Builder do
+ provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email"
+end
diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb
new file mode 100644
index 0000000000..bbfc3961bf
--- /dev/null
+++ b/config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters format: [:json]
+end
+
+# To enable root element in JSON for ActiveRecord objects.
+# ActiveSupport.on_load(:active_record) do
+# self.include_root_in_json = true
+# end
diff --git a/config/locales/en.yml b/config/locales/en.yml
new file mode 100644
index 0000000000..decc5a8573
--- /dev/null
+++ b/config/locales/en.yml
@@ -0,0 +1,33 @@
+# Files in the config/locales directory are used for internationalization
+# and are automatically loaded by Rails. If you want to use locales other
+# than English, add the necessary files in this directory.
+#
+# To use the locales, use `I18n.t`:
+#
+# I18n.t 'hello'
+#
+# In views, this is aliased to just `t`:
+#
+# <%= t('hello') %>
+#
+# To use a different locale, set it with `I18n.locale`:
+#
+# I18n.locale = :es
+#
+# This would use the information in config/locales/es.yml.
+#
+# The following keys must be escaped otherwise they will not be retrieved by
+# the default I18n backend:
+#
+# true, false, on, off, yes, no
+#
+# Instead, surround them with single quotes.
+#
+# en:
+# 'true': 'foo'
+#
+# To learn more, please read the Rails Internationalization guide
+# available at http://guides.rubyonrails.org/i18n.html.
+
+en:
+ hello: "Hello world"
diff --git a/config/puma.rb b/config/puma.rb
new file mode 100644
index 0000000000..1e19380dcb
--- /dev/null
+++ b/config/puma.rb
@@ -0,0 +1,56 @@
+# Puma can serve each request in a thread from an internal thread pool.
+# The `threads` method setting takes two numbers: a minimum and maximum.
+# Any libraries that use thread pools should be configured to match
+# the maximum value specified for Puma. Default is set to 5 threads for minimum
+# and maximum; this matches the default thread size of Active Record.
+#
+threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
+threads threads_count, threads_count
+
+# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
+#
+port ENV.fetch("PORT") { 3000 }
+
+# Specifies the `environment` that Puma will run in.
+#
+environment ENV.fetch("RAILS_ENV") { "development" }
+
+# Specifies the number of `workers` to boot in clustered mode.
+# Workers are forked webserver processes. If using threads and workers together
+# the concurrency of the application would be max `threads` * `workers`.
+# Workers do not work on JRuby or Windows (both of which do not support
+# processes).
+#
+# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
+
+# Use the `preload_app!` method when specifying a `workers` number.
+# This directive tells Puma to first boot the application and load code
+# before forking the application. This takes advantage of Copy On Write
+# process behavior so workers use less memory. If you use this option
+# you need to make sure to reconnect any threads in the `on_worker_boot`
+# block.
+#
+# preload_app!
+
+# If you are preloading your application and using Active Record, it's
+# recommended that you close any connections to the database before workers
+# are forked to prevent connection leakage.
+#
+# before_fork do
+# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
+# end
+
+# The code in the `on_worker_boot` will be called if you are using
+# clustered mode by specifying a number of `workers`. After each worker
+# process is booted, this block will be run. If you are using the `preload_app!`
+# option, you will want to use this block to reconnect to any threads
+# or connections that may have been created at application boot, as Ruby
+# cannot share connections between processes.
+#
+# on_worker_boot do
+# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
+# end
+#
+
+# Allow puma to be restarted by `rails restart` command.
+plugin :tmp_restart
diff --git a/config/routes.rb b/config/routes.rb
new file mode 100644
index 0000000000..cc8a00136b
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,43 @@
+Rails.application.routes.draw do
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+ root "main#index"
+ get "/auth/:provider/callback", to: "merchants#login", as: 'login'
+ get '/logout', to: 'merchants#logout', as: 'logout'
+ resources :merchants, except: [:index, :new] do
+ resources :orders, only: [:index]
+ resources :order_products, only:[:index], as: 'sold'
+ end
+
+ resources :orders
+ resources :order_products, only: [:update, :destroy]
+ get '/products/:id/categories', to: 'categories#add', as: 'add_categories'
+ post '/products/:id/categories', to: 'products#add_categories'
+ post '/products/:id/new_category', to: 'categories#create'
+ resources :products
+ resources :categories, only: [:new, :create, :destroy] do
+ resources :products, only: [:index]
+ end
+
+ resources :products do
+ resources :reviews, only: [:new]
+ end
+ resources :reviews, only: [:create, :edit, :update, :destroy]
+
+ post '/products/:id', to: 'main#add_to_cart', as: 'add_to_cart'
+ get '/cart', to: 'main#shopping_cart', as: 'shopping_cart'
+
+ # User pages
+ get '/home', to: 'merchants#summary', as: 'self_summary'
+ get '/pending', to: 'merchants#pending', as: 'self_pending'
+ get '/completed', to: 'merchants#completed', as: 'self_completed'
+ get '/revenue', to: 'merchants#revenue', as: 'self_revenue'
+ get '/inventory', to: 'merchants#inventory', as: 'self_inventory'
+
+ get '/:id/mark_shipped', to: 'merchants#mark_shipped', as: 'mark_shipped'
+ get '/:id/retire', to: 'products#retire', as: 'retire_product'
+
+ get '/confirmation/:id', to: 'orders#confirmation', as: 'confirmation'
+
+ # Order cancel
+ get '/:id/cancel', to: 'orders#cancel', as: 'cancel'
+end
diff --git a/config/secrets.yml b/config/secrets.yml
new file mode 100644
index 0000000000..72b9e72e41
--- /dev/null
+++ b/config/secrets.yml
@@ -0,0 +1,32 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key is used for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+# You can use `rails secret` to generate a secure secret key.
+
+# Make sure the secrets in this file are kept private
+# if you're sharing your code publicly.
+
+# Shared secrets are available across all environments.
+
+# shared:
+# api_key: a1B2c3D4e5F6
+
+# Environmental secrets are only available for that specific environment.
+
+development:
+ secret_key_base: d8797e95f21baa346e31bb4faa5dcea245a15a27f2ded487fc5d4ade2c6e74b160fb66be42573345b02d608424a56e6282b69803cb7236738bc220dd3cfa9905
+
+test:
+ secret_key_base: 27c6cf01738639692c9961e53a4110dc2a2c272aed16e8db3e4dd109fd4d47f4d68a2008b12795a9b5b493ed29293839e838ed9ef597b85343941e1782a9f291
+
+# Do not keep production secrets in the unencrypted secrets file.
+# Instead, either read values from the environment.
+# Or, use `bin/rails secrets:setup` to configure encrypted secrets
+# and move the `production:` environment over there.
+
+production:
+ secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
diff --git a/config/spring.rb b/config/spring.rb
new file mode 100644
index 0000000000..c9119b40c0
--- /dev/null
+++ b/config/spring.rb
@@ -0,0 +1,6 @@
+%w(
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+).each { |path| Spring.watch(path) }
diff --git a/db/migrate/20171017230149_create_merchants.rb b/db/migrate/20171017230149_create_merchants.rb
new file mode 100644
index 0000000000..262cacfc5e
--- /dev/null
+++ b/db/migrate/20171017230149_create_merchants.rb
@@ -0,0 +1,9 @@
+class CreateMerchants < ActiveRecord::Migration[5.1]
+ def change
+ create_table :merchants do |t|
+ t.string :username
+ t.string :email
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20171017230651_create_orders.rb b/db/migrate/20171017230651_create_orders.rb
new file mode 100644
index 0000000000..8523638ae9
--- /dev/null
+++ b/db/migrate/20171017230651_create_orders.rb
@@ -0,0 +1,14 @@
+class CreateOrders < ActiveRecord::Migration[5.1]
+ def change
+ create_table :orders do |t|
+ t.string :cust_name
+ t.string :status
+ t.string :cust_email
+ t.string :cust_cc
+ t.string :cust_cc_exp
+ t.string :cust_addr
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20171017231112_create_products.rb b/db/migrate/20171017231112_create_products.rb
new file mode 100644
index 0000000000..05f78884a1
--- /dev/null
+++ b/db/migrate/20171017231112_create_products.rb
@@ -0,0 +1,13 @@
+class CreateProducts < ActiveRecord::Migration[5.1]
+ def change
+ create_table :products do |t|
+ t.string :name
+ t.string :image_url
+ t.string :category
+ t.float :price
+ t.integer :quantity
+ t.text :description
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20171017231524_create_reviews.rb b/db/migrate/20171017231524_create_reviews.rb
new file mode 100644
index 0000000000..6952d5ad07
--- /dev/null
+++ b/db/migrate/20171017231524_create_reviews.rb
@@ -0,0 +1,10 @@
+class CreateReviews < ActiveRecord::Migration[5.1]
+ def change
+ create_table :reviews do |t|
+ t.integer :rating
+ t.string :description
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20171017231907_create_order_products.rb b/db/migrate/20171017231907_create_order_products.rb
new file mode 100644
index 0000000000..948b400eec
--- /dev/null
+++ b/db/migrate/20171017231907_create_order_products.rb
@@ -0,0 +1,9 @@
+class CreateOrderProducts < ActiveRecord::Migration[5.1]
+ def change
+ create_table :order_products do |t|
+ t.string :status
+ t.integer :quantity
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20171018021532_add_foreign_keys.rb b/db/migrate/20171018021532_add_foreign_keys.rb
new file mode 100644
index 0000000000..026fc5cd99
--- /dev/null
+++ b/db/migrate/20171018021532_add_foreign_keys.rb
@@ -0,0 +1,13 @@
+class AddForeignKeys < ActiveRecord::Migration[5.1]
+ def change
+ add_column :order_products, :order_id, :integer
+ add_column :order_products, :product_id, :integer
+
+ add_column :products, :merchant_id, :integer
+
+ add_column :orders, :merchant_id, :integer
+
+ add_column :reviews, :merchant_id, :integer
+ add_column :reviews, :product_id, :integer
+ end
+end
diff --git a/db/migrate/20171018190422_add_oauth_data_to_merchants.rb b/db/migrate/20171018190422_add_oauth_data_to_merchants.rb
new file mode 100644
index 0000000000..1243f9d815
--- /dev/null
+++ b/db/migrate/20171018190422_add_oauth_data_to_merchants.rb
@@ -0,0 +1,6 @@
+class AddOauthDataToMerchants < ActiveRecord::Migration[5.1]
+ def change
+ add_column :merchants, :oauth_provider, :string
+ add_column :merchants, :oauth_uid, :string
+ end
+end
diff --git a/db/migrate/20171018191131_require_oauth_parameters_for_merchants.rb b/db/migrate/20171018191131_require_oauth_parameters_for_merchants.rb
new file mode 100644
index 0000000000..252d41c8b9
--- /dev/null
+++ b/db/migrate/20171018191131_require_oauth_parameters_for_merchants.rb
@@ -0,0 +1,6 @@
+class RequireOauthParametersForMerchants < ActiveRecord::Migration[5.1]
+ def change
+ change_column_null :merchants, :oauth_uid, false
+ change_column_null :merchants, :oauth_provider, false
+ end
+end
diff --git a/db/migrate/20171020221429_create_categories.rb b/db/migrate/20171020221429_create_categories.rb
new file mode 100644
index 0000000000..5bef4913b8
--- /dev/null
+++ b/db/migrate/20171020221429_create_categories.rb
@@ -0,0 +1,9 @@
+class CreateCategories < ActiveRecord::Migration[5.1]
+ def change
+ create_table :categories do |t|
+ t.string :name
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20171020221607_create_categories_products.rb b/db/migrate/20171020221607_create_categories_products.rb
new file mode 100644
index 0000000000..b87a74eb79
--- /dev/null
+++ b/db/migrate/20171020221607_create_categories_products.rb
@@ -0,0 +1,8 @@
+class CreateCategoriesProducts < ActiveRecord::Migration[5.1]
+ def change
+ create_table :categories_products do |t|
+ t.integer :category_id
+ t.integer :product_id
+ end
+ end
+end
diff --git a/db/migrate/20171020222435_remove_column_products_category_string.rb b/db/migrate/20171020222435_remove_column_products_category_string.rb
new file mode 100644
index 0000000000..2410329855
--- /dev/null
+++ b/db/migrate/20171020222435_remove_column_products_category_string.rb
@@ -0,0 +1,5 @@
+class RemoveColumnProductsCategoryString < ActiveRecord::Migration[5.1]
+ def change
+ remove_column :products, :category, :string
+ end
+end
diff --git a/db/migrate/20171025214645_add_cvv_and_zip_code_to_orders.rb b/db/migrate/20171025214645_add_cvv_and_zip_code_to_orders.rb
new file mode 100644
index 0000000000..6fe5142931
--- /dev/null
+++ b/db/migrate/20171025214645_add_cvv_and_zip_code_to_orders.rb
@@ -0,0 +1,6 @@
+class AddCvvAndZipCodeToOrders < ActiveRecord::Migration[5.1]
+ def change
+ add_column :orders, :cvv, :string
+ add_column :orders, :zip_code, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000000..d0e5d70ec2
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,81 @@
+# 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: 20171025214645) do
+
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
+
+ create_table "categories", force: :cascade do |t|
+ t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "categories_products", force: :cascade do |t|
+ t.integer "category_id"
+ t.integer "product_id"
+ end
+
+ create_table "merchants", force: :cascade do |t|
+ t.string "username"
+ t.string "email"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "oauth_provider", null: false
+ t.string "oauth_uid", null: false
+ end
+
+ create_table "order_products", force: :cascade do |t|
+ t.string "status"
+ t.integer "quantity"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "order_id"
+ t.integer "product_id"
+ end
+
+ create_table "orders", force: :cascade do |t|
+ t.string "cust_name"
+ t.string "status"
+ t.string "cust_email"
+ t.string "cust_cc"
+ t.string "cust_cc_exp"
+ t.string "cust_addr"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "merchant_id"
+ t.string "cvv"
+ t.string "zip_code"
+ end
+
+ create_table "products", force: :cascade do |t|
+ t.string "name"
+ t.string "image_url"
+ t.float "price"
+ t.integer "quantity"
+ t.text "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "merchant_id"
+ end
+
+ create_table "reviews", force: :cascade do |t|
+ t.integer "rating"
+ t.string "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "merchant_id"
+ t.integer "product_id"
+ end
+
+end
diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100644
index 0000000000..92ef85cd58
--- /dev/null
+++ b/db/seeds.rb
@@ -0,0 +1,104 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
+#
+# Examples:
+#
+# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
+# Character.create(name: 'Luke', movie: movies.first)
+
+merchants = Merchant.create([
+ { username: 'Eagle', email: 'watchingyou@gmail.com', id: 1, oauth_provider: 'github', oauth_uid: '123456', created_at: Time.now, updated_at: Time.now },
+ { username: 'Glimpie', email: 'mememe@hotmail.com', id: 2, oauth_provider: 'github', oauth_uid: '123457', created_at: Time.now, updated_at: Time.now },
+ { username: 'Earthborn King', email: 'eking@something.com', id: 3, oauth_provider: 'github', oauth_uid: '123458', created_at: Time.now, updated_at: Time.now },
+ { username: 'MagicBrian', email: 'brian@spider.com', id: 4, oauth_provider: 'github', oauth_uid: '123459', created_at: Time.now, updated_at: Time.now },
+ { username: 'Taako', email: 'abra@cafu.com', id: 5, oauth_provider: 'github', oauth_uid: '123460', created_at: Time.now, updated_at: Time.now },
+ { username: 'Forest Nymph', email: 'inatree@treepeople.com', id: 6, oauth_provider: 'github', oauth_uid: '123461', created_at: Time.now, updated_at: Time.now },
+ { username: 'Bridge Troll', email: 'trolltoll@bridge.com', id: 7, oauth_provider: 'github', oauth_uid: '123462', created_at: Time.now, updated_at: Time.now },
+ { username: 'Puck', email: 'sneakyelf@shakespeare.com', id: 8, oauth_provider: 'github', oauth_uid: '123463', created_at: Time.now, updated_at: Time.now }
+ ])
+
+orders = Order.create([
+ { cust_name: 'Elvy', status: 'paid', cust_email: 'elvii@gmail.com', cust_cc: '11111000000', cust_cc_exp: '02/18', cust_addr: 'Elves Land', id: 1, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Earthborn King', status: 'paid', cust_email: 'eking@something.com', cust_cc: '22222220000000', cust_cc_exp: '10/19', cust_addr: 'Magestic Castle VI', id: 2, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Eagle', status: 'completed', cust_email: 'watchingyou@gmail.com', cust_cc: '3333333000000', cust_cc_exp: '04/18', cust_addr: '2nd Rock ', id: 3, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Maeraly', status: 'completed', cust_email: 'maeraly@hotmail.com', cust_cc: '4444000000', cust_cc_exp: '02/21', cust_addr: '123 75th Line ', id: 4, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Sar Squatch', status: 'completed', cust_email: 'big@feet.com', cust_cc: '1234567890', cust_cc_exp: '06/19', cust_addr: 'The Woods', id: 5, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Ponyboy', status: 'paid', cust_email: 'big@feet.com', cust_cc: '1234552567890', cust_cc_exp: '06/19', cust_addr: 'Cartoon Network', id: 6, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Maleficent', status: 'completed', cust_email: 'veryevilfairy@hotmail.com', cust_cc: '1234567823423490', cust_cc_exp: '04/25', cust_addr: '12345 Perceforest', id: 7, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Witchy Witcherson', status: 'completed', cust_email: 'witch@witch.com', cust_cc: '1234567890234234', cust_cc_exp: '06/22', cust_addr: '294 Witch St, Witchville', id: 8, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Angry Waitress', status: 'completed', cust_email: 'dontmesswithbess@restaurant.com', cust_cc: '1234567890', cust_cc_exp: '10/90', cust_addr: 'The Restaurant', id: 9, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'DND Master', status: 'paid', cust_email: 'dragon_person@email.com', cust_cc: '1234567890', cust_cc_exp: '10/20', cust_addr: 'Fantasy Land', id: 10, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Ialways Wearmask', status: 'completed', cust_email: 'standingoveryouwiththatcreepymask@yourhouse.com', cust_cc: '1234567890', cust_cc_exp: '10/20', cust_addr: 'Charbroiled Goodness St', id: 11, created_at: Time.now, updated_at: Time.now },
+ { cust_name: 'Hermit Wizard', status: 'completed', cust_email: 'lookingforlove@lonelyville.com', cust_cc: '1234567890', cust_cc_exp: '10/20', cust_addr: 'The cave', id: 12, created_at: Time.now, updated_at: Time.now }
+ ])
+
+categories = Category.create([
+ {name: 'Magic Ingredients', id: 1},
+ {name: 'Legendary Items', id: 2},
+ {name: 'Mythical Creatures', id: 3},
+ {name: 'Enchanted Food', id: 4},
+ {name: 'Apparel', id: 5},
+ {name: 'Curses', id: 6}
+ ])
+
+products = Product.create([
+ { name: 'Peaches of Immortality ', image_url: 'http://4.bp.blogspot.com/-5GQ1fBLNVr4/UAM6EnUpUSI/AAAAAAAAEyg/XKNYdq4P8x4/s1600/6a00e55119b5ec8834012877293a88970c-800wi.jpg', price: 3.25, quantity: 120, description: "Magical powers of immortality given from one bite", id: 1, created_at: Time.now, updated_at: Time.now, merchant_id: 4 },
+ { name: 'That Ring', image_url: 'https://listverse.com/wp-content/uploads/2013/08/the-one-ring-e1375311691730.jpg', price: 350.00, quantity: 1, description: "The ring that gives you invisibility", id: 2, created_at: Time.now, updated_at: Time.now, merchant_id: 4 },
+ {name: 'Dragon\'s Tooth', image_url: 'https://img1.etsystatic.com/100/0/9170080/il_340x270.831176717_5zaz.jpg', price: 9.99, quantity: 4, description: "Gives magical powers to it's owner.", id: 3, created_at: Time.now, updated_at: Time.now, merchant_id: 4 },
+ { name: 'Helm of Darkness', image_url: 'http://cdn3.list25.com/wp-content/uploads/2017/04/HELMOFDARKNESSorsimilarmaybesubstituted-610x610.jpg', price: 49.99, quantity: 25, description: "Turn into a shadow with this magical helmet.", id: 4, created_at: Time.now, updated_at: Time.now , merchant_id: 5 },
+ { name: 'Umbra Staff', image_url: 'https://image.freepik.com/free-photo/umbrella-closed_19-121570.jpg', price: 693.39, quantity: 10, description: "This is a supremely powerful magic wand, imbued with the soul of a great wizard. It's definitely not just an umbrella.", id: 5, created_at: Time.now, updated_at: Time.now, merchant_id: 6 },
+ { name: 'My Little Pony (REAL)', image_url: 'https://lilyladewig.files.wordpress.com/2009/04/glitter-pony-2.jpg', price: 123.45, quantity: 100, description: "REAL PONY REAL MAGIC!! NOT FAKE", id: 6, created_at: Time.now, updated_at: Time.now, merchant_id: 7 },
+ { name: 'Witch\'s Tears', image_url: 'https://i.ebayimg.com/images/g/BlsAAOSwWnFV84vC/s-l300.jpg', price: 21.21, quantity: 50, description: "You have to be really mean to a witch to make these.", id: 7, created_at: Time.now, updated_at: Time.now, merchant_id: 8 },
+ {name: 'Golden Fleece', image_url: 'https://i.imgur.com/UMHsPJn.jpg', price: 5000.39 , quantity: 1, description: "Fresh Golden Fleece.", id: 8, created_at: Time.now, updated_at: Time.now, merchant_id: 7 },
+ { name: 'Excalibur', image_url: 'https://i.imgur.com/2cGiWjQ.jpg', price: 50.01, quantity: 1, description: "Do you want to be the King of Camelot? Here is your chance. Also, people used to be a lot smaller than they are now.", id: 9, created_at: Time.now, updated_at: Time.now, merchant_id: 6 },
+ { name: 'Ghost of Caesar in a Mason Jar', image_url: 'https://i.imgur.com/ys1of8Rr.jpg', price: 2145.96, quantity: 1, description: "There are so many uses for this ghost in a jar. Are you interested in learning Latin? Have someone you really need to scare? This ghost will haunt like none other. Just open the jar and your home, work, or back yard is haunted by Julius Caesar. You'll be saying 'E Tu, Brute' in no time.", id: 10, created_at: Time.now, updated_at: Time.now, merchant_id: 4 },
+ { id: 11, name: 'Monkey\'s Paw', image_url: 'https://theluckycandle.com/wp-content/uploads/2014/07/Monkey-Paw-.jpg', price: 777.00, quantity: 60, description: 'The paw of a monkey, said to grant any wish in unfathomable ways.(Seller does not take any responsibility for the outcome of said wishes)', created_at: Time.now, updated_at: Time.now, merchant_id: 3 },
+ { id: 12, name: 'Bloodied Parchments of Merlin', image_url: 'https://orig00.deviantart.net/e72e/f/2011/259/a/1/bloody_parchment_by_shinjite_samachan-d4a2dlf.jpg', price: 20, quantity: 300, description: 'Parchments said to come from the infamous wizard himself. Papers are bloodied to the point of illegibility.', created_at: Time.now, updated_at: Time.now, merchant_id: 4 },
+ { id: 13, name: 'Golden Saddles of Mercury', image_url: 'https://i.pinimg.com/736x/3a/b2/b6/3ab2b6bc38cc66bbdad7c4cca2b02812.jpg', price: 104.67, quantity: 9, description: 'Sturdy leather saddle set for horse-riding enthusiasts. Recommended for winged steeds.', created_at: Time.now, updated_at: Time.now, merchant_id: 5 },
+ { id: 14, name: 'Mermaid Fin', image_url: 'https://www.finfunmermaid.com/images/products/dragonfly-mermaid-tail_category.jpg', price: 35.00, quantity: 2017, description: 'Fins collected from mermaids who decided to give up their underwater life to be where the (land) people are.', created_at: Time.now, updated_at: Time.now, merchant_id: 6 }
+ ])
+
+p = Product.find(1); p.categories << [Category.find(4), Category.find(1)]; p.save
+p = Product.find(2); p.categories << Category.find(2); p.save
+p = Product.find(3); p.categories << Category.find(1); p.save
+p = Product.find(4); p.categories << Category.find(5); p.save
+p = Product.find(5); p.categories << [Category.find(2), Category.find(4)]; p.save
+p = Product.find(6); p.categories << Category.find(3); p.save
+p = Product.find(7); p.categories << Category.find(1); p.save
+p = Product.find(8); p.categories << Category.find(1); p.save
+p = Product.find(9); p.categories << Category.find(2); p.save
+p = Product.find(10); p.categories << Category.find(6); p.save
+p = Product.find(11); p.categories << [Category.find(6), Category.find(1), Category.find(2)]; p.save
+p = Product.find(12); p.categories << Category.find(1); p.save
+p = Product.find(13); p.categories << Category.find(2); p.save
+p = Product.find(14); p.categories << Category.find(3); p.save
+
+order_products = OrderProduct.create([
+ { status: 'shipped', quantity: '1', order_id: 1, product_id: 13, created_at: Time.now, updated_at: Time.now, id: 1 },
+ { status: 'shipped', quantity: '3', order_id: 2, product_id: 12, created_at: Time.now, updated_at: Time.now, id: 2 },
+ { status: 'shipped', quantity: '1', order_id: 3, product_id: 11, created_at: Time.now, updated_at: Time.now, id: 3 },
+ { status: 'shipped', quantity: '3', order_id: 4, product_id: 10, created_at: Time.now, updated_at: Time.now, id: 4 },
+ { status: 'shipped', quantity: '1', order_id: 5, product_id: 8, created_at: Time.now, updated_at: Time.now, id: 5 },
+ { status: 'shipped', quantity: '3', order_id: 6, product_id: 7, created_at: Time.now, updated_at: Time.now, id: 6 },
+ { status: 'shipped', quantity: '1', order_id: 7, product_id: 9, created_at: Time.now, updated_at: Time.now, id: 7 },
+ { status: 'shipped', quantity: '3', order_id: 8, product_id: 1, created_at: Time.now, updated_at: Time.now, id: 8 },
+ { status: 'shipped', quantity: '1', order_id: 9, product_id: 2, created_at: Time.now, updated_at: Time.now, id: 9 },
+ { status: 'shipped', quantity: '3', order_id: 10, product_id: 3, created_at: Time.now, updated_at: Time.now, id: 10 },
+ { status: 'shipped', quantity: '1', order_id: 11, product_id: 4, created_at: Time.now, updated_at: Time.now, id: 11 },
+ { status: 'shipped', quantity: '3', order_id: 12, product_id: 5, created_at: Time.now, updated_at: Time.now, id: 12 },
+ { status: 'shipped', quantity: '1', order_id: 11, product_id: 6, created_at: Time.now, updated_at: Time.now, id: 13 },
+ { status: 'shipped', quantity: '3', order_id: 10, product_id: 7, created_at: Time.now, updated_at: Time.now, id: 14 },
+ { status: 'shipped', quantity: '1', order_id: 9, product_id: 8, created_at: Time.now, updated_at: Time.now, id: 15 },
+ { status: 'shipped', quantity: '3', order_id: 8, product_id: 9, created_at: Time.now, updated_at: Time.now, id: 16 },
+ { status: 'shipped', quantity: '1', order_id: 7, product_id: 14, created_at: Time.now, updated_at: Time.now, id: 17 }
+ ])
+
+reviews = Review.create([
+ { rating: 5, description: 'very nice and magic', product_id: 6, created_at: Time.now, updated_at: Time.now, id: 1 },
+ { rating: 1, description: 'these are wizards tears, NOT witch\'s tears. seller should be ashamed', product_id: 7, created_at: Time.now, updated_at: Time.now, id: 2 }
+ ])
+
+puts "Manually resetting PK sequence on each table"
+ActiveRecord::Base.connection.tables.each do |t|
+ ActiveRecord::Base.connection.reset_pk_sequence!(t)
+end
diff --git a/lib/assets/.keep b/lib/assets/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lib/tasks/.keep b/lib/tasks/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/log/.keep b/log/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..f874acf437
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "betsy",
+ "private": true,
+ "dependencies": {}
+}
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 0000000000..2be3af26fc
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,67 @@
+
+
+
+ The page you were looking for doesn't exist (404)
+
+
+
+
+
+
+
+
+
The page you were looking for doesn't exist.
+
You may have mistyped the address or the page may have moved.
+
+
If you are the application owner check the logs for more information.
+
+
+
diff --git a/public/422.html b/public/422.html
new file mode 100644
index 0000000000..c08eac0d1d
--- /dev/null
+++ b/public/422.html
@@ -0,0 +1,67 @@
+
+
+
+ The change you wanted was rejected (422)
+
+
+
+
+
+
+
+
+
The change you wanted was rejected.
+
Maybe you tried to change something you didn't have access to.
+
+
If you are the application owner check the logs for more information.
+
+
+
diff --git a/public/500.html b/public/500.html
new file mode 100644
index 0000000000..78a030af22
--- /dev/null
+++ b/public/500.html
@@ -0,0 +1,66 @@
+
+
+
+ We're sorry, but something went wrong (500)
+
+
+
+
+
+
+
+
+
We're sorry, but something went wrong.
+
+
If you are the application owner check the logs for more information.
+
+
+
diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000000..37b576a4a0
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1 @@
+# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
new file mode 100644
index 0000000000..d19212abd5
--- /dev/null
+++ b/test/application_system_test_case.rb
@@ -0,0 +1,5 @@
+require "test_helper"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+end
diff --git a/test/controllers/.keep b/test/controllers/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb
new file mode 100644
index 0000000000..33855fd8b1
--- /dev/null
+++ b/test/controllers/categories_controller_test.rb
@@ -0,0 +1,50 @@
+require "test_helper"
+
+describe CategoriesController do
+
+ let(:params) {
+ { "category_name" => "Test",
+ "product_id" => Product.first.id
+ }
+ }
+
+ describe 'create' do
+ describe 'logged in users' do
+ before do
+ user = merchants(:ada)
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user))
+ get login_path(:github)
+ end
+
+ it 'can be created by a logged-in user with valid data' do
+ before_count = Category.count
+ post categories_path, params: params
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ Category.count.must_equal (before_count + 1)
+ end
+
+ it 'cannot be created by a logged-in user with invalid data' do
+ params['category_name'] = ""
+ before_count = Category.count
+ post categories_path, params: params
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ Category.count.must_equal (before_count)
+ end
+ end
+
+ describe 'guest users' do
+ it 'cannot be created by a guest user' do
+ before_count = Category.count
+ post categories_path, params: params
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ Category.count.must_equal (before_count)
+ end
+ end
+ end
+end
diff --git a/test/controllers/main_controller_test.rb b/test/controllers/main_controller_test.rb
new file mode 100644
index 0000000000..af3db93f5c
--- /dev/null
+++ b/test/controllers/main_controller_test.rb
@@ -0,0 +1,58 @@
+require "test_helper"
+
+describe MainController do
+ let(:wand) { products(:wand) }
+
+ it 'can successfully access the index page' do
+ get root_path
+
+ must_respond_with :success
+ end
+
+ it 'can successfully access the shopping cart page' do
+ get shopping_cart_path
+
+ must_respond_with :success
+ end
+
+ describe '#add_to_cart' do
+ it 'adds items to cart' do
+ params = {
+ "quantity" => "4",
+ "id" => wand.id
+ }
+
+ post add_to_cart_path(wand.id), params: params
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ session[:cart].length.must_equal 1
+ end
+
+ it 'returns not_found for DNE product' do
+ bad_id = Product.last.id + 1
+ params = {
+ "quantity" => "4",
+ "id" => bad_id
+ }
+
+ post add_to_cart_path(bad_id), params: params
+
+ must_respond_with :not_found
+ session[:cart].length.must_equal 0
+ end
+
+ it 'wont work if no quantity is passed' do
+ params = {
+ "id" => wand.id
+ }
+
+ post add_to_cart_path(wand.id), params: params
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+
+ session[:cart].length.must_equal 0
+ end
+ end
+end
diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb
new file mode 100644
index 0000000000..2817f0fbee
--- /dev/null
+++ b/test/controllers/merchants_controller_test.rb
@@ -0,0 +1,295 @@
+require "test_helper"
+
+describe MerchantsController do
+
+ describe "show" do
+ it "succeeds for an extant merchant" do
+ get merchant_path(Merchant.first)
+ must_respond_with :success
+ end
+
+ it "renders 404 not_found for a bogus merchant" do
+ bogus_merchant_id = Merchant.last.id + 1
+ get merchant_path(bogus_merchant_id)
+ must_respond_with :not_found
+ end
+ end
+
+ describe "login" do
+
+ it "logs in an existing merchant and redirects to the root route" do
+ start_count = Merchant.all.count
+
+ merchant = merchants(:grace)
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(merchant))
+
+ get login_path(:github)
+
+ must_redirect_to root_path
+
+ session[:merchant][:id].must_equal merchant.id
+
+ Merchant.all.count.must_equal start_count
+ end
+
+ it "creates new user with new info" do
+
+ start_count = Merchant.all.count
+ new_merchant = Merchant.new(oauth_provider: 'github',oauth_uid: "adlgkcl",email: 'mail@mail.com', username: 'Mr. New')
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(new_merchant))
+ get login_path(:github)
+ must_redirect_to root_path
+ Merchant.count.must_equal start_count + 1
+ session[:merchant][:id].must_equal Merchant.last.id
+ flash[:result_text].must_equal "Successfully created new merchant "
+ flash[:status].must_equal :success
+
+ end
+
+ it "generates failure with no info" do
+ start_count = Merchant.all.count
+ OmniAuth.config.mock_auth[:github] = nil
+ get login_path(:github)
+ Merchant.count.must_equal start_count
+ flash[:result_text].must_equal "Not logged in"
+ flash[:status].must_equal :failure
+ flash[:messages].must_include :email
+ flash[:messages].must_include :username
+ must_redirect_to root_path
+ end
+
+ end
+
+ describe "logout" do
+ it "logs merchants out" do
+ get logout_path
+ session[:merchant].must_equal nil
+ must_redirect_to root_path
+ end
+ end
+
+ describe "create" do
+ it "works" do
+ merchant = {
+ merchant: {
+ oauth_provider: "github",
+ oauth_uid: "99999",
+ email: "somebody@somesite.com",
+ username: "Somebody"
+ }
+ }
+ Merchant.new(merchant[:merchant]).must_be :valid?
+ start_count = Merchant.count
+ post merchants_path, params: merchant
+ flash[:status].must_equal :success
+ flash[:message].must_include "Successfully created merchant"
+ must_respond_with :redirect
+ must_redirect_to root_path
+ Merchant.count.must_equal start_count + 1
+ end
+
+
+ it "re-renders form when the merchant data is invalid" do
+ merchant = {
+ merchant: {
+ oauth_provider: "github",
+ oauth_uid: "",
+ email: "somebodyelse@somesite.com",
+ username: "Somebody_else"
+ }
+ }
+ Merchant.new(merchant[:merchant]).wont_be :valid?
+ start_count = Merchant.count
+
+ post merchants_path, params: merchant
+
+ must_respond_with :bad_request
+ Merchant.count.must_equal start_count
+ end
+ end
+
+ describe "edit" do
+ it "succeeds for an extant merchant ID" do
+ get edit_merchant_path(Merchant.first)
+ must_respond_with :success
+ end
+ it "renders 404 not_found for a bogus merchant ID" do
+ bogus_merchant_id = Merchant.last.id + 1
+ get edit_merchant_path(bogus_merchant_id)
+ must_respond_with :not_found
+ end
+ end
+ describe "destroy" do
+ it "removes the merchant and goes to the root path" do
+ first_count = Merchant.all.count
+ delete_merchant = Merchant.first
+ delete merchant_path(delete_merchant)
+ must_respond_with :redirect
+ must_redirect_to root_path
+ Merchant.all.count.must_equal first_count - 1
+
+ end
+ end
+ describe "update" do
+
+ it "returns success if the merchant ID is valid and the change is valid" do
+ merchant = Merchant.first
+ merchant_data = {
+ merchant: {
+ username: "changed username",
+ email: merchant.email
+ }
+ }
+ merchant.update_attributes(merchant_data[:merchant])
+ merchant.must_be :valid?, "Test is invalid because the provided data will produce an invalid merchant"
+
+ patch merchant_path(merchant), params: merchant_data
+
+ must_respond_with :redirect
+ must_redirect_to merchant_path(merchant)
+
+ # Check that the change went through
+ merchant.reload
+ merchant.username.must_equal merchant_data[:merchant][:username]
+ end
+ #
+ it "returns not_found if the merchant ID is invalid" do
+ invalid_merchant_id = Merchant.last.id + 1
+ merchant_data = {
+ merchant: {
+ username: "Changed username",
+ merchant_id: Merchant.first.id
+ }
+ }
+
+ patch merchant_path(invalid_merchant_id), params: merchant_data
+
+ must_respond_with :not_found
+ end
+ #
+ it "returns bad_request if the change is invalid" do
+ merchant = Merchant.first
+ invalid_merchant_data = {
+ merchant: {
+ email: ""
+ }
+ }
+ # Check that the update is actually invalid
+ merchant.update_attributes(invalid_merchant_data[:merchant])
+ merchant.wont_be :valid?
+ patch merchant_path(merchant), params: invalid_merchant_data
+
+ must_respond_with :bad_request
+
+ merchant.reload
+ merchant.username.wont_equal invalid_merchant_data[:merchant][:username]
+ end
+ end
+
+ describe "user pages" do
+ describe "logged in" do
+ before do
+ user = merchants(:ada)
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user))
+ get login_path(:github)
+ end
+
+ it 'logged in user can access summary' do
+ get self_summary_path
+
+ must_respond_with :success
+ end
+
+ it 'logged in user can access pending' do
+ get self_pending_path
+
+ must_respond_with :success
+ end
+
+ it 'logged in user can access completed' do
+ get self_completed_path
+
+ must_respond_with :success
+ end
+
+ it 'logged in user can access revenue' do
+ get self_summary_path
+
+ must_respond_with :success
+ end
+
+ it 'logged in user can access inventory' do
+ get self_summary_path
+
+ must_respond_with :success
+ end
+
+ describe 'mark_shipped' do
+ it 'changes the status of an existing product owned by the user' do
+ op_id = order_products(:order_products).id
+ get mark_shipped_path(op_id)
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ OrderProduct.find(op_id).status.must_equal "shipped"
+ end
+
+ it 'returns not_found if product does not exist' do
+ get mark_shipped_path(OrderProduct.last.id + 1)
+
+ must_respond_with :not_found
+ end
+
+ it 'redirects without changing if product does not belong to user' do
+ op_id = order_products(:other_order_products).id
+ get mark_shipped_path(op_id)
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ OrderProduct.find(op_id).status.wont_equal "shipped"
+ end
+ end
+ end
+
+ describe "guests" do
+ it 'guest user cannot access summary' do
+ get self_summary_path
+
+ must_respond_with :found
+ end
+
+ it 'guest user cannot access pending' do
+ get self_pending_path
+
+ must_respond_with :found
+ end
+
+ it 'guest user cannot access completed' do
+ get self_completed_path
+
+ must_respond_with :found
+ end
+
+ it 'guest user cannot access revenue' do
+ get self_summary_path
+
+ must_respond_with :found
+ end
+
+ it 'guest user cannot access inventory' do
+ get self_summary_path
+
+ must_respond_with :found
+ end
+
+ it 'guest user cannot access mark_shipped' do
+ op_id = order_products(:order_products).id
+ get mark_shipped_path(op_id)
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ OrderProduct.find(op_id).status.wont_equal "shipped"
+ end
+ end
+ end
+end
diff --git a/test/controllers/order_products_controller_test.rb b/test/controllers/order_products_controller_test.rb
new file mode 100644
index 0000000000..6553c37621
--- /dev/null
+++ b/test/controllers/order_products_controller_test.rb
@@ -0,0 +1,75 @@
+require "test_helper"
+
+describe OrderProductsController do
+ describe "index" do
+ it "returns success for the order products of a specific merchant if given a merchant" do
+ merchant = :ada
+ get merchant_sold_index_path(merchant)
+ must_respond_with :success
+ end
+ end
+
+ describe "update" do
+ it "returns success if order product exists and changes are valid" do
+ order_product = OrderProduct.first
+ changes = {
+ order_product: {
+ quantity: 2
+ }
+ }
+ order_product.update_attributes(changes[:order_product])
+ order_product.must_be :valid?
+
+ patch order_product_path(order_product), params: changes
+ must_respond_with :redirect
+ must_redirect_to shopping_cart_path
+
+ order_product.reload
+ order_product.quantity.must_equal changes[:order_product][:quantity]
+ end
+
+ it "returns failure if order product exists and changes are invalid" do
+ order_product = OrderProduct.first
+ changes = {
+ order_product: {
+ quantity: 900000
+ }
+ }
+ patch order_product_path(order_product), params: changes
+ order_product.quantity.wont_equal 900000
+ end
+
+ it "returns not_found if order product does not exist" do
+ order_product = OrderProduct.first
+ changes = {
+ order_product: {
+ quantity: 2
+ }
+ }
+ order_product.update_attributes(changes[:order_product])
+ order_product.must_be :valid?
+ order_product.destroy
+
+ patch order_product_path(order_product), params: changes
+ must_respond_with :not_found
+ end
+ end
+
+ describe "destroy" do
+ it "returns success and destroys if order product exists" do
+ order_product = OrderProduct.first
+ order_product.must_be :valid?
+ delete order_product_path(order_product)
+ must_respond_with :redirect
+ must_redirect_to shopping_cart_path
+ end
+
+ it "returns not_found if order product does not exist" do
+ order_product = OrderProduct.first
+ order_product.must_be :valid?
+ delete order_product_path(order_product)
+ delete order_product_path(order_product)
+ must_respond_with :not_found
+ end
+ end
+end
diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb
new file mode 100644
index 0000000000..bedcb6c29e
--- /dev/null
+++ b/test/controllers/orders_controller_test.rb
@@ -0,0 +1,171 @@
+require "test_helper"
+
+describe OrdersController do
+ describe "index" do
+ it "returns success for the orders of a specific merchant if given a merchant" do
+ merchant = :ada
+ login_test_user
+ get merchant_orders_path(merchant)
+ must_respond_with :success
+ end
+
+ it "will not show page to guest users" do
+ merchant = :ada
+ get merchant_orders_path(merchant)
+ must_redirect_to root_path
+ end
+ end
+
+ describe "new" do
+ it "returns success for a new orders" do
+ get new_order_path
+ must_respond_with :success
+ end
+ end
+
+ describe "create" do
+ it "adds order to the database with at least 1 order product and redirects when the data is valid" do
+ login_test_user
+ cart_params = {
+ "quantity" => 4,
+ "id" => products(:wand).id
+ }
+ post add_to_cart_path(products(:wand).id), params: cart_params
+
+ order = {
+ order: {
+ cust_name: "Mermaid",
+ merchant_id: 21,
+ cust_cc: 12345,
+ cust_cc_exp: "11/22",
+ cust_addr: "Sea World",
+ cust_email: "forkhair@mermaid.com",
+ cvv: 123,
+ zip_code: 32444,
+ status: "complete",
+ created_at: Time.now,
+ updated_at: Time.now
+ }
+ }
+ good_order = Order.new(order[:order])
+ good_order.must_be :valid?
+ start_count = Order.count
+
+ post orders_path, params: order
+
+ must_respond_with :redirect
+ must_redirect_to confirmation_path(Order.last.id)
+ Order.count.must_equal start_count + 1
+ flash[:status].must_equal :success
+ flash[:message].must_include "Successfully created order"
+ end
+
+ it "re-renders form when the order data is invalid" do
+ order = {
+ order: {
+ cust_name: "Mermaid",
+ merchant_id: 21,
+ cust_cc: 12345,
+ cust_cc_exp: "11/22",
+ cust_addr: "Sea World",
+ cvv: 123,
+ zip_code: 32444,
+ cust_email: "forkhair@mermaid.com",
+ status: ""
+ }
+ }
+ Order.new(order[:order]).wont_be :valid?
+ start_count = Order.count
+
+ post orders_path, params: order
+
+ must_respond_with :bad_request
+ Order.count.must_equal start_count
+ end
+ end
+
+ describe "show" do
+ it "returns success with valid id when logged in" do
+ login_test_user
+ order_id = Order.first.id
+ get order_path(order_id)
+ must_respond_with :success
+ end
+
+ it "returns not_found with invalid id when logged in" do
+ login_test_user
+ invalid_id = Order.last.id + 1
+ get order_path(invalid_id)
+ must_respond_with :not_found
+ end
+ end
+
+ describe "edit" do
+ it "returns success when the order belongs to the merchant" do
+ login_test_user
+ cart_params = {
+ "quantity" => 4,
+ "id" => products(:wand).id
+ }
+ post add_to_cart_path(products(:wand).id), params: cart_params
+ order = {
+ order: {
+ cust_name: "Mermaid",
+ merchant_id: merchants(:ada).id,
+ cust_cc: 12345,
+ cust_cc_exp: "11/22",
+ cust_addr: "Sea World",
+ cust_email: "forkhair@mermaid.com",
+ cvv: 123,
+ zip_code: 32444,
+ status: "complete",
+ created_at: Time.now,
+ updated_at: Time.now
+ }
+ }
+ order = Order.create!(order[:order])
+ order_id = Order.last.id
+ get edit_order_path(order_id)
+ must_respond_with :success
+ end
+ it "returns failure when the order does not belong to the merchant" do
+ end
+ end
+
+ describe "update" do
+ it "returns success if at least 1 order product exists and changes are valid" do
+ login_test_user
+ order = Order.first
+ changes = {
+ order: {
+ cust_name: "Wienerschnitzel"
+ }
+ }
+ order.update_attributes(changes[:order])
+ order.must_be :valid?
+
+ patch order_path(order), params: changes
+
+ must_respond_with :redirect
+ must_redirect_to order_path(order.id)
+ order.reload
+ order.cust_name.must_equal changes[:order][:cust_name]
+ end
+
+ it "returns failure if at least 1 order product exists and changes are invalid" do
+ login_test_user
+ order = Order.first
+ changes = {
+ order: {
+ cust_name: ""
+ }
+ }
+ order.update_attributes(changes[:order])
+ order.wont_be :valid?
+
+ patch order_path(order), params: changes
+
+ must_respond_with :bad_request
+ end
+ end
+end
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
new file mode 100644
index 0000000000..69a2d84725
--- /dev/null
+++ b/test/controllers/products_controller_test.rb
@@ -0,0 +1,304 @@
+require "test_helper"
+
+describe ProductsController do
+ let(:prod) { products(:mermaid_fin) }
+ let(:good_params) { { product: { "name"=>"New Test Item", "price"=>"11.00", "quantity"=>"1", "description"=>"This is a test" } } }
+ let(:bad_params) { { product: { "name"=>"" } } }
+ let(:tmi_params) { { product: { "name"=>"New Test Item", "price"=>"11.00", "quantity"=>"1", "description"=>"This is a test", "tmi"=>"uh oh", "id"=>1 } } }
+ let(:new_params) { { product: { "name"=>"Updated Name" } } }
+ let(:owned_product) { owned_product = Product.find_by(merchant_id: merchants(:ada).id) }
+ let(:not_owned_product) { not_owned_product = Product.find_by(merchant_id: merchants(:grace).id) }
+
+ before do
+ @before_count = Product.count
+ end
+
+ describe 'for logged in users' do
+ before do
+ user = merchants(:ada)
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user))
+ get login_path(:github)
+ end
+
+ describe 'index' do
+ it 'can successfully access index of products' do
+ get products_path
+
+ must_respond_with :success
+ end
+
+ it 'can successfully access nested index of products with valid id' do
+ get category_products_path(Category.first.id)
+
+ controller.instance_variables.must_include :@cat
+ must_respond_with :success
+ end
+
+ it 'returns 404 for invalid category ID in nested route' do
+ get products_path, params: { 'category_id' => (Category.last.id + 1) }
+
+ must_respond_with :not_found
+ end
+ end
+
+ describe 'show' do
+ it 'can successfully access show product with valid ID' do
+ get product_path(prod.id)
+
+ must_respond_with :success
+ end
+
+ it 'returns 404 with invalid ID' do
+ get product_path( Product.last.id + 1 )
+
+ must_respond_with :not_found
+ end
+ end
+
+ it 'can successfully access new product' do
+ get new_product_path
+
+ must_respond_with :success
+ end
+
+ describe 'create' do
+ it 'can successfully create valid product' do
+ post products_path, params: good_params
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ Product.count.must_equal (@before_count + 1)
+ end
+
+ it 'will not create new product with invalid data' do
+ post products_path, params: bad_params
+
+ must_respond_with :bad_request
+ flash[:status].must_equal :failure
+ Product.count.must_equal @before_count
+ end
+
+ it 'uses strong params' do
+ post products_path, params: tmi_params
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ Product.last.id.wont_equal 1
+ Product.count.must_equal (@before_count + 1)
+ end
+ end
+
+ describe 'edit' do
+ it 'can successfully access edit for own product' do
+ get edit_product_path(owned_product.id)
+
+ must_respond_with :success
+ end
+
+ it 'CANNOT successfully access edit for not found product (returns not found)' do
+ get edit_product_path(Product.last.id + 1)
+
+ must_respond_with :not_found
+ end
+
+ it 'CANNOT successfully edit other users products' do
+ get edit_product_path(not_owned_product.id)
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ end
+ end
+
+ describe 'update' do
+ it 'can successfully update own product & redirect to product show page with valid data' do
+ patch product_path(owned_product.id), params: new_params, headers: { "HTTP_REFERER" => edit_product_path(owned_product.id) }
+ id = owned_product.id
+
+ Product.find(id).name.must_equal "Updated Name"
+ flash[:status].must_equal :success
+ must_redirect_to product_path
+ must_respond_with :found
+ end
+
+ it 'redirects to inventory path if coming from inventory management page' do
+ patch product_path(owned_product.id), params: new_params, headers: { "HTTP_REFERER" => self_inventory_path }
+ id = owned_product.id
+
+ Product.find(id).name.must_equal "Updated Name"
+ flash[:status].must_equal :success
+ must_redirect_to self_inventory_path
+ must_respond_with :found
+ end
+
+ it 'CANNOT successfully update own product with invalid data' do
+ patch product_path(owned_product.id), params: bad_params
+
+ must_respond_with :bad_request
+ flash[:status].must_equal :failure
+ end
+
+ it 'CANNOT successfully update other users product' do
+ patch product_path(not_owned_product.id), params: new_params
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ end
+ end
+
+ describe 'destroy' do
+ it 'can successfully destroy own product' do
+ delete product_path(owned_product.id)
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ Product.count.must_equal (@before_count - 1)
+ end
+
+ it 'CANNOT destroy other users products' do
+ delete product_path(not_owned_product.id)
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ Product.count.must_equal @before_count
+ end
+ end
+
+ describe 'categories' do
+ it 'can access page for own products' do
+ get add_categories_path(owned_product.id)
+
+ must_respond_with :success
+ end
+
+ it 'CANNOT access page for other users products' do
+ get add_categories_path(not_owned_product.id)
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ end
+ end
+
+ describe 'add_categories' do
+ it 'can successfully add valid categories to own products' do
+ cat_id = categories(:magic).id
+ test_params = {
+ id: owned_product.id,
+ "category_Magic Items" => cat_id,
+ }
+ before_count = owned_product.categories.count
+
+ post add_categories_path(owned_product.id), params: test_params
+
+ must_respond_with :found
+ flash[:status].must_equal :success
+ owned_product.categories.count.must_equal (before_count + 1)
+ end
+
+ it 'CANNOT add invalid categories to own products' do
+ cat_id = categories(:magic).id
+ test_params = {
+ id: owned_product.id,
+ "category_bad_id" => (Category.last.id + 1),
+ }
+
+ before_count = owned_product.categories.count
+ post add_categories_path(owned_product.id), params: test_params
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ owned_product.categories.count.must_equal before_count
+ end
+
+ it 'CANNOT successfully add categories to other users products' do
+ test_params = { "product_id"=>"2",
+ "category_Magic Items"=>"5",
+ id: :id
+ }
+
+ post add_categories_path(not_owned_product.id), params: test_params
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ end
+ end
+ end
+
+ describe 'for guest users' do
+ describe 'index' do
+ it 'can successfully access index of products' do
+ get products_path
+
+ must_respond_with :success
+ end
+
+ it 'can successfully access nested index of products with valid id' do
+ get category_products_path(Category.first.id)
+
+ controller.instance_variables.must_include :@cat
+ must_respond_with :success
+ end
+
+ it 'returns 404 for invalid category ID in nested route' do
+ get products_path, params: { 'category_id' => (Category.last.id + 1) }
+
+ must_respond_with :not_found
+ end
+ end
+
+ describe 'show' do
+ it 'can successfully access show product with valid ID' do
+ get product_path(prod.id)
+
+ must_respond_with :success
+ end
+
+ it 'returns 404 with invalid ID' do
+ get product_path( Product.last.id + 1 )
+
+ must_respond_with :not_found
+ end
+ end
+
+ it 'CANNOT access new product' do
+ get new_product_path(prod.id)
+
+ flash[:status].must_equal :failure
+ must_respond_with :found
+ end
+
+ it 'CANNOT create valid product' do
+ post products_path(prod.id), params: good_params
+
+ flash[:status].must_equal :failure
+ must_respond_with :found
+ end
+
+ it 'CANNOT access edit for product' do
+ get edit_product_path(prod.id)
+
+ flash[:status].must_equal :failure
+ must_respond_with :found
+ end
+
+ it 'CANNOT update product' do
+ patch product_path(prod.id), params: good_params
+
+ flash[:status].must_equal :failure
+ must_respond_with :found
+ end
+
+ it 'CANNOT destroy other users products' do
+ delete product_path(prod.id)
+
+ flash[:status].must_equal :failure
+ must_respond_with :found
+ end
+
+ it 'CANNOT access add categories page' do
+ get add_categories_path(owned_product.id)
+
+ must_respond_with :found
+ flash[:status].must_equal :failure
+ end
+ end
+end
diff --git a/test/controllers/reviews_controller_test.rb b/test/controllers/reviews_controller_test.rb
new file mode 100644
index 0000000000..85350a0c32
--- /dev/null
+++ b/test/controllers/reviews_controller_test.rb
@@ -0,0 +1,222 @@
+require "test_helper"
+
+describe ReviewsController do
+ let(:review) { reviews(:review) }
+ let(:mermaid_fin) { products(:mermaid_fin) }
+ let(:wand) { products(:wand) }
+
+######### Leaving for future, if we rethink and decide to add later
+ # describe "index" do
+ # it "returns success status for all reviews" do
+ # get reviews_path
+ # must_respond_with :success
+ # end
+ #
+ # it "works when there are no reviews" do
+ # Review.destroy_all
+ # get reviews_path
+ # must_respond_with :success
+ # end
+ # end
+
+ # describe "show" do
+ # it "succeeds for a review that exists" do
+ # get review_path(reviews(:review))
+ # must_respond_with :success
+ # end
+ #
+ # it "returns 404 not_found for a review that does not exist" do
+ # get review_path(123)
+ # must_respond_with :not_found
+ # end
+ # end
+
+ describe "new" do
+
+ it " should not work if product doesn't exist" do
+ bad_product = Product.last.id + 1
+ get new_product_review_path(bad_product)
+
+ must_respond_with :not_found
+ end
+
+ it " should work for a non-user" do
+ logout_test_user
+ get new_product_review_path(mermaid_fin)
+ must_respond_with :success
+ end
+
+ it " should not work if product belongs to merchant " do
+ # Arrange
+ login_test_user
+
+ # Act
+ get new_product_review_path(mermaid_fin)
+
+ # Assert
+ must_respond_with :redirect
+ must_redirect_to product_path(mermaid_fin)
+ end
+ end
+
+ describe "create" do
+
+ # We are already checking "new" method for owenership
+ # it "should return failure if product belongs to merchant " do
+ # # arrange: login 'ada' user
+ # login_test_user
+ # review_data = {
+ # review: {
+ # rating: 5,
+ # product_id: mermaid_fin.id
+ # }
+ # }
+ # start_review_count = Review.count
+ # # binding.pry
+ # # act: review (Mermaid Fin)
+ # post reviews_path, params: review_data
+ #
+ # # assert: expect failure.
+ # must_respond_with :redirect
+ # must_redirect_to product_path(mermaid_fin)
+ # Review.count.must_equal start_review_count
+ # end
+
+ it "adds the review to the product and redirects when the review data is valid" do
+ # Arrange
+ review_data = {
+ review: {
+ rating: 5,
+ product_id: mermaid_fin.id
+ }
+ }
+ start_review_count = Review.count
+ # Act
+ post reviews_path, params: review_data
+ # Assert
+ must_redirect_to product_path(mermaid_fin)
+ Review.count.must_equal start_review_count + 1
+ end
+
+
+ it "sends bad_request if review data is invalid" do
+ # Arrange
+ invalid_review_data = {
+ review: {
+ #No rating
+ product_id: mermaid_fin.id
+ }
+ }
+ # Double checking the data is truly invalid
+ Review.new(invalid_review_data[:review]).wont_be :valid?
+ start_review_count = Review.count
+
+ # Act
+ post reviews_path, params: invalid_review_data
+
+ # Assert
+ must_respond_with :bad_request
+ Review.count.must_equal start_review_count
+ end
+ end
+
+ describe "edit" do
+
+ before do
+ login_test_user
+ end
+
+ it "will return failure if merchant owns the product" do
+ # login_test_user
+
+ get edit_review_path(review)
+
+ must_redirect_to product_path(mermaid_fin)
+ end
+
+ it "succeeds for an exact review ID" do
+
+ review_id = Review.first.id
+ get edit_review_path(review.id)
+ must_respond_with :found
+ end
+
+ it "renders 404 not_found for a bogus review ID" do
+ bogus_review_id = Review.last.id + 1
+ get edit_review_path(bogus_review_id)
+ must_respond_with :not_found
+ end
+ end
+
+ describe "update" do
+
+ it "will successfully update review" do
+ review_data = {
+ review: {
+ rating: 3,
+ product: mermaid_fin.attributes
+ }
+ }
+
+ patch review_path(review.id), params: review_data
+
+ must_respond_with :redirect
+ must_redirect_to product_path(mermaid_fin)
+
+ Review.find(review.id).rating.must_equal 3
+ end
+
+ it "will return not found, if review doesn't exist" do
+ bad_id = Review.last.id + 1
+ review_data = {
+ review:{
+ rating: 3,
+ description: "It is a good product "
+ }
+ }
+ patch review_path(bad_id), params: review_data
+ must_respond_with :not_found
+ end
+
+ it "will not let you invalidate review" do
+ review_data = {
+ review: {
+ rating: 'shdfgsjh',
+ product: mermaid_fin.attributes
+ }
+ }
+
+ patch review_path(review.id), params: review_data
+
+ must_respond_with :bad_request
+ end
+ end
+
+ describe "destroy" do
+
+ before do
+ login_test_user
+ end
+
+ it "will successfully destroy review" do
+
+ start_review_count = Review.count
+
+ delete review_path(reviews(:review4))
+ # binding.pry
+ must_redirect_to product_path(wand.id)
+
+ flash[:status].must_equal :success
+ Review.count.must_equal start_review_count - 1
+ end
+
+ it "will not delete review if merchant owns the product" do
+ # login_test_user
+ start_review_count = Review.count
+ delete review_path(review)
+
+ must_redirect_to product_path(mermaid_fin.id)
+ Review.count.must_equal start_review_count
+ end
+ end
+end
diff --git a/test/fixtures/.keep b/test/fixtures/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
new file mode 100644
index 0000000000..deee5548f9
--- /dev/null
+++ b/test/fixtures/categories.yml
@@ -0,0 +1,7 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+magic:
+ name: Magic Items
+
+food:
+ name: Food
diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml
new file mode 100644
index 0000000000..fee9544e95
--- /dev/null
+++ b/test/fixtures/merchants.yml
@@ -0,0 +1,18 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the "{}" from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+
+ada:
+ oauth_provider: github
+ oauth_uid: 12345
+ email: ada@adadevelopersacademy.org
+ username: countess_ada
+
+grace:
+ oauth_provider: github
+ oauth_uid: 13371337
+ email: grace@hooper.net
+ username: graceful_hoops
diff --git a/test/fixtures/order_products.yml b/test/fixtures/order_products.yml
new file mode 100644
index 0000000000..0ca4de9018
--- /dev/null
+++ b/test/fixtures/order_products.yml
@@ -0,0 +1,31 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ quantity: 2
+ product: mermaid_fin
+ order: order
+ status: pending
+
+two:
+ quantity: 3
+ product: mermaid_fin
+ order: order
+ status: shipped
+
+order_products:
+ quantity: 2
+ product: mermaid_fin
+ order: order
+ status: pending
+
+other_order_products:
+ quantity: 3
+ product: wand
+ order: order
+ status: pending
+
+guest_op:
+ quantity: 5
+ product: mermaid_fin
+ order: guest_order
+ status: pending
diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml
new file mode 100644
index 0000000000..8b21c8ea8c
--- /dev/null
+++ b/test/fixtures/orders.yml
@@ -0,0 +1,33 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+order:
+ cust_name: Mermaid
+ merchant_id: :ada
+ cust_cc: 12345
+ cust_cc_exp: 11/22
+ cvv: 111
+ zip_code: 32444
+ cust_addr: Sea World
+ cust_email: forkhair@mermaid.com
+ status: pending
+
+other_order:
+ cust_name: Demon
+ merchant_id: :grace
+ cust_cc: 5432314
+ cust_cc_exp: 66/66
+ cvv: 111
+ zip_code: 32444
+ cust_addr: Fire World
+ cust_email: flameshair@mermaid.com
+ status: pending
+
+guest_order:
+ cust_name: Gaston
+ cust_cc: 1234098756
+ cust_cc_exp: 12/48
+ cvv: 324
+ zip_code: 56123
+ cust_addr: A small provincial town
+ cust_email: gaston@beautybeast.com
+ status: pending
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
new file mode 100644
index 0000000000..b0c666f4f2
--- /dev/null
+++ b/test/fixtures/products.yml
@@ -0,0 +1,17 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the "{}" from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+mermaid_fin:
+ name: Mermaid Fin
+ price: 3.45
+ quantity: 30
+ merchant: ada
+
+wand:
+ name: Magic Wand
+ price: 10.14
+ quantity: 10
+ merchant: grace
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
new file mode 100644
index 0000000000..479d5e3d4d
--- /dev/null
+++ b/test/fixtures/reviews.yml
@@ -0,0 +1,25 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the "{}" from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+
+review:
+ rating: 4
+ merchant: ada
+ product: mermaid_fin
+
+review2:
+ rating: 3
+ merchant: ada
+ product: mermaid_fin
+
+review3:
+ rating: 4
+ merchant: grace
+ product: wand
+
+review4:
+ rating: 4
+ merchant: ada
+ product: wand
diff --git a/test/helpers/.keep b/test/helpers/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/helpers/helpers_test.rb b/test/helpers/helpers_test.rb
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/integration/.keep b/test/integration/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/mailers/.keep b/test/mailers/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/models/.keep b/test/models/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/models/category_test.rb b/test/models/category_test.rb
new file mode 100644
index 0000000000..3d54153e03
--- /dev/null
+++ b/test/models/category_test.rb
@@ -0,0 +1,42 @@
+require "test_helper"
+
+describe Category do
+ let(:c) { Category.new(name: "Test") }
+
+ describe 'validations' do
+ it 'can be valid' do
+ c.must_be :valid?
+ end
+
+ describe 'name' do
+ it 'must be present' do
+ c.name.clear
+
+ c.wont_be :valid?
+ c.errors.messages.must_include :name
+ end
+
+ it 'must be unique' do
+ d = c.dup
+ d.save
+
+ c.wont_be :valid?
+ c.errors.messages.must_include :name
+ end
+
+ it 'tests uniqueness - case sensitive' do
+ d = Category.new(name: "TEST")
+ d.save
+
+ c.wont_be :valid?
+ c.errors.messages.must_include :name
+ end
+ end
+ end
+
+ describe 'relationships' do
+ it 'responds to products' do
+ c.must_respond_to :products
+ end
+ end
+end
diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb
new file mode 100644
index 0000000000..f22ff95b31
--- /dev/null
+++ b/test/models/merchant_test.rb
@@ -0,0 +1,146 @@
+require "test_helper"
+
+describe Merchant do
+ let(:merchant) { Merchant.new( username: 'testman',
+ email: 'test@test.com',
+ oauth_provider: 'github',
+ oauth_uid: '1234567')
+ }
+
+ # it "must be valid" do
+ # value(merchant).must_be :valid?
+ # end
+
+ describe 'validations' do
+ it 'can be created with valid data' do
+ merchant.must_be :valid?
+ end
+
+ describe 'username' do
+ it 'requires username' do
+ merchant.username = nil
+ merchant.email = 'test@test.com'
+ merchant.oauth_provider = 'github'
+ merchant.oauth_uid = '1234567'
+ merchant.save
+ merchant.errors.messages.must_include :username
+ merchant.wont_be :valid?
+ end
+ it "has a unique username" do
+ merchant.username = "countess_ada"
+ merchant.wont_be :valid?
+ merchant.errors.messages.must_include :username
+ end
+ end
+
+ describe "username email validations" do
+ describe "tests for format of email" do
+
+ it "will reject a bad email" do
+ merchant.email = "I'm an email"
+ merchant.wont_be :valid?
+ merchant.errors.messages.must_include :email
+ end
+ end
+
+ describe "tests for presence of email" do
+ it "will reject missing email" do
+ merchant.email = nil
+ merchant.wont_be :valid?
+ merchant.errors.messages.must_include :email
+ end
+ end
+ end
+
+ describe "oauth validations" do
+ it "will reject not unique oauth_uid" do
+ merchant.oauth_uid = "12345"
+ merchant.wont_be :valid?
+ merchant.errors.messages.must_include :oauth_uid
+ end
+ end
+
+ describe "oauth_provider" do
+ it "will be rejected if oauth_provider not present" do
+ merchant.oauth_provider = nil
+ merchant.wont_be :valid?
+ merchant.errors.messages.must_include :oauth_provider
+ end
+ end
+ end
+
+ describe 'relationships' do
+ it "has a list of order_products" do
+ mermaid_fin = merchants(:ada)
+ mermaid_fin.must_respond_to :products
+ mermaid_fin.products.each do |product|
+ product.must_be_kind_of Product
+ end
+ end
+
+ it "has a list of reviews" do
+ review = merchants(:ada)
+ review.must_respond_to :reviews
+ review.reviews.each do |review|
+ review.must_be_kind_of Review
+ end
+ end
+
+ it "has a list of orders" do
+ merchant = merchants(:ada)
+ merchant.must_respond_to :orders
+ merchant.orders.each do |order|
+ order.must_be_kind_of Order
+ end
+ end
+
+ it "has a list of order products" do
+ order_product = merchants(:ada)
+ order_product.must_respond_to :order_products
+ end
+ end
+ describe "self.by_auth_hash(auth_hash)" do
+ it "has an instance of merchant" do
+ auth_hash = {
+ 'provider' => "github",
+ 'uid' => "12345",
+ 'info' => {'email' => "somebody@somesite.com",
+ 'nickname' => "Somebody"}
+ }
+ merchant_auth = Merchant.by_auth_hash auth_hash
+ merchant_auth.must_be_kind_of Merchant
+ # merchant_auth.must_respond_to
+ # binding.pry
+ end
+
+ it "responds to the proper keys" do
+ auth_hash = {
+ 'provider' => "github",
+ 'uid' => "12345",
+ 'info' => {'email' => "somebody@somesite.com",
+ 'nickname' => "Somebody"}
+ }
+ merchant_auth = Merchant.by_auth_hash auth_hash
+ merchant_auth.must_respond_to :oauth_provider
+ merchant_auth.must_respond_to :oauth_uid
+ merchant_auth.must_respond_to :email
+ merchant_auth.must_respond_to :username
+ # binding.pry
+ end
+
+ it "has the correct values for merchant from auth_hash" do
+ auth_hash = {
+ 'provider' => "github",
+ 'uid' => "12345",
+ 'info' => {'email' => "somebody@somesite.com",
+ 'nickname' => "Somebody"}
+ }
+ merchant_auth = Merchant.by_auth_hash auth_hash
+ merchant_auth.oauth_provider.must_equal "github"
+ merchant_auth.oauth_uid.must_equal "12345"
+ merchant_auth.email.must_equal "somebody@somesite.com"
+ merchant_auth.username.must_equal "Somebody"
+ end
+
+ end
+ end
diff --git a/test/models/order_product_test.rb b/test/models/order_product_test.rb
new file mode 100644
index 0000000000..4a254a568e
--- /dev/null
+++ b/test/models/order_product_test.rb
@@ -0,0 +1,40 @@
+require "test_helper"
+
+describe OrderProduct do
+ let(:order_product) { order_products(:one)}
+
+ describe "validations" do
+ describe "quantity" do
+ it "should return false without a quantity" do
+ order_product.valid?.must_equal true
+ order_product.quantity = nil
+ order_product.valid?.must_equal false
+ end
+
+ it "should return false if quantity is not greater than 0 " do
+ order_product.quantity = 0
+ order_product.valid?.must_equal false
+ end
+ end
+ end
+
+ describe "relationships" do
+ it "has a product" do
+ b = order_products(:one)
+ a = products(:mermaid_fin)
+
+ b.must_respond_to :product
+ b.product.must_equal a
+ b.product_id.must_equal a.id
+ end
+
+ it "has an order" do
+ b = order_products(:one)
+ a = orders(:order)
+
+ b.must_respond_to :order
+ b.order.must_equal a
+ b.order_id.must_equal a.id
+ end
+ end
+end
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
new file mode 100644
index 0000000000..45c691a0d2
--- /dev/null
+++ b/test/models/order_test.rb
@@ -0,0 +1,83 @@
+require "test_helper"
+
+describe Order do
+ let(:bad_order) { Order.new(
+ merchant_id: :grace,
+ cust_name: "John",
+ cust_cc: "12345",
+ cust_cc_exp: "11/22",
+ cust_addr: "Sea World",
+ cust_email: "forkhair@mermaid.com",
+ status: "")
+ }
+
+ describe "validations" do
+ it "can be created with valid data" do
+ orders(:order).must_be :valid?
+ end
+ it "won't be valid without status" do
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :status
+ end
+ it "won't be valid without cust_name" do
+ bad_order.status = "complete"
+ bad_order.cust_name = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_name
+ end
+ it "won't be valid without :cust_cc" do
+ bad_order.cust_cc = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_cc
+ end
+ it "won't be valid without :cust_cc_exp" do
+ bad_order.cust_cc_exp = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_cc_exp
+ end
+ it "won't be valid without :cust_addr" do
+ bad_order.cust_addr = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_addr
+ end
+ it "won't be valid without :cust_name" do
+ bad_order.cust_name = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_name
+ end
+ it "won't be valid without :cust_email" do
+ bad_order.cust_email = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_email
+ end
+ it "won't be valid without :cust_email" do
+ bad_order.cust_email = ""
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_email
+ end
+ it "won't be valid if email format is bad" do
+ bad_order.cust_email = "Email"
+ bad_order.wont_be :valid?
+ bad_order.errors.messages.must_include :cust_email
+ end
+ end
+ describe "relationships" do
+ describe "order products" do
+ it "contains an array of Order Products" do
+ orders(:order).order_products.each do |order_product|
+ order_product.must_be_kind_of OrderProduct
+ end
+ end
+ it "contains an array of Products" do
+ orders(:order).products.each do |product|
+ product.must_be_kind_of Product
+ end
+ end
+ end
+ describe "Merchant relationship" do
+ it "can have a Merchant" do
+ orders(:order).must_respond_to :merchant
+ end
+ end
+ end
+end
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
new file mode 100644
index 0000000000..c0c617f0f6
--- /dev/null
+++ b/test/models/product_test.rb
@@ -0,0 +1,160 @@
+require "test_helper"
+
+describe Product do
+ let(:product) { products(:mermaid_fin) }
+
+ describe 'validations' do
+ it 'accepts valid data' do
+ product.must_be :valid?
+ end
+
+ describe 'name' do
+ it 'requires name to be present' do
+ product.name = nil
+
+ product.wont_be :valid?
+ product.errors.messages.must_include :name
+ end
+
+ it 'requires name to be unique' do
+ prod2 = Product.new(
+ name: 'Mermaid Fin',
+ price: 1.11,
+ quantity: 10,
+ merchant_id: merchants(:grace).id
+ )
+
+ prod2.wont_be :valid?
+ prod2.errors.messages.must_include :name
+ end
+ end
+
+ describe 'price' do
+ it 'requires price to be present' do
+ product.price = nil
+
+ product.wont_be :valid?
+ product.errors.messages.must_include :price
+ end
+
+ it 'requires price to be a number greater than 0' do
+ # Rails automatically converts most invalid inputs to 0. This input would not be accepted.
+ # Potential issues: 'true' converts to 1
+ # Does this require a custom validation? Is there a scenario in which a user could accidentally enter 'true'?
+ [-15, 'hey', false, nil, 0].each do |value|
+ product.price = value
+
+ product.wont_be :valid?
+ product.errors.messages.must_include :price
+ end
+ end
+ end
+
+ describe 'quantity' do
+ it 'requires quantity to be present' do
+ product.quantity = nil
+
+ product.wont_be :valid?
+ product.errors.messages.must_include :quantity
+ end
+
+ it 'requires quantity to be an integer greater than or equal to 0' do
+ # Rails automatically converts most invalid inputs to 0. An item won't display with a quantity of 0, but it will save, which isn't ideal.
+ # Other potential issues: 'true' converts to 1, any float is just rounded down
+ # Does this require a custom validation?
+ product.quantity = -1
+
+ product.wont_be :valid?
+ product.errors.messages.must_include :quantity
+ end
+ end
+ end
+
+ describe 'relationships' do
+ it 'has order_products' do
+ product.must_respond_to :order_products
+ product.order_products.each do |op|
+ op.must_be_kind_of OrderProduct
+ end
+ end
+
+ it 'has reviews' do
+ product.must_respond_to :reviews
+ product.reviews.each do |review|
+ review.must_be_kind_of Review
+ end
+ end
+
+ it 'has orders' do
+ product.must_respond_to :orders
+ product.orders do |order|
+ order.must_be_kind_of Order
+ end
+ end
+
+ it 'has merchant' do
+ product.must_respond_to :merchant
+ product.merchant.must_be_kind_of Merchant
+ end
+ end
+
+ describe 'most_popular' do
+ it 'returns an array of 6 products if database contains at least 6 products' do
+ 1..6.times do |i|
+ Product.create!(name: i, price: i+1, quantity: i, merchant_id: Merchant.first.id)
+ end
+ prods = Product.most_popular
+
+ prods.length.must_equal 6
+ end
+
+ it 'returns a shorter array of products if database less than 6 products' do
+ if Product.count >= 6
+ i = Product.count
+ until i < 6
+ Product.last.destroy
+ end
+ end
+ prods = Product.most_popular
+
+ prods.length.must_equal Product.count
+ end
+
+ it 'orders array by number of orders for product' do
+ prods = Product.most_popular
+ prods.each_cons(2) do |pair|
+ (pair[0].orders.count >= pair[1].orders.count).must_equal true
+ end
+ end
+
+ it 'returns an empty array if database contains no products' do
+ Product.destroy_all
+
+ Product.most_popular.must_equal []
+ end
+ end
+
+ describe 'add_categories_by_params' do
+ it 'adds provided categories to product' do
+ test_categories = [Category.first, Category.last]
+ test_params = { 'category_test_0' => test_categories[0].id,
+ 'category_test_1' => test_categories[1].id }
+
+ product.add_categories_by_params(test_params)
+
+ product.categories.must_include test_categories[0]
+ product.categories.must_include test_categories[1]
+ end
+
+ it 'wont duplicate categories within product' do
+ test_category = Category.first
+ test_params = { 'category_test' => test_category.id }
+
+ count = product.categories.count
+
+ test_params = { 'category_test' => test_category.id }
+
+ product.categories.count.must_equal count
+ end
+ end
+end
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
new file mode 100644
index 0000000000..00f5369328
--- /dev/null
+++ b/test/models/review_test.rb
@@ -0,0 +1,79 @@
+require "test_helper"
+
+describe Review do
+ let(:review) { reviews(:review) }
+
+ describe "validations" do
+ it "will return false without a rating" do
+ review.rating = nil
+ review.valid?.must_equal false
+ end
+
+ it "will return false if rating is not an integer" do
+ review.rating = 3.1
+ review.valid?.must_equal false
+ end
+
+ it "will return false if rating is not in range of 1-5" do
+ review.rating = 6
+ review.valid?.must_equal false
+
+ review.rating = 0
+ review.valid?.must_equal false
+ end
+ end
+
+ describe "relations" do
+ it 'belongs to merchants' do
+ Review.reflect_on_association(:merchant)
+ end
+
+ it "valid even if it does not belong to a merchant" do
+ review.merchant = nil
+ review.valid?.must_equal true
+ end
+
+ it 'belongs to products' do
+ Review.reflect_on_association(:product)
+ end
+
+ it "invalid if it has no product" do
+ review.product_id = nil
+ review.valid?.must_equal false
+ end
+ end
+
+ describe "get_rating_stars" do
+ it 'returns 1 star if given a rating of 0-1' do
+ Review.get_rating_stars(0).must_equal "⭑⭒⭒⭒⭒"
+ Review.get_rating_stars(0.5).must_equal "⭑⭒⭒⭒⭒"
+ Review.get_rating_stars(1.0).must_equal "⭑⭒⭒⭒⭒"
+ end
+
+ it 'returns 2 star if given a rating of 1-2' do
+ Review.get_rating_stars(1.5).must_equal "⭑⭑⭒⭒⭒"
+ Review.get_rating_stars(2).must_equal "⭑⭑⭒⭒⭒"
+ end
+
+ it 'returns 3 star if given a rating of 2-3' do
+ Review.get_rating_stars(2.5).must_equal "⭑⭑⭑⭒⭒"
+ Review.get_rating_stars(3).must_equal "⭑⭑⭑⭒⭒"
+ end
+
+ it 'returns 4 star if given a rating of 3-4' do
+ Review.get_rating_stars(3.5).must_equal "⭑⭑⭑⭑⭒"
+ Review.get_rating_stars(4).must_equal "⭑⭑⭑⭑⭒"
+ end
+
+ it 'returns 5 star if given a rating of greater than 4' do
+ Review.get_rating_stars(4.5).must_equal "⭑⭑⭑⭑⭑"
+ Review.get_rating_stars(5).must_equal "⭑⭑⭑⭑⭑"
+ Review.get_rating_stars(6).must_equal "⭑⭑⭑⭑⭑"
+ end
+
+ it 'returns empty string if rating is nil or empty' do
+ Review.get_rating_stars("").must_equal ""
+ Review.get_rating_stars(nil).must_equal ""
+ end
+ end
+end
diff --git a/test/system/.keep b/test/system/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000000..6b7f7c378f
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,57 @@
+require 'simplecov'
+SimpleCov.start
+ENV["RAILS_ENV"] = "test"
+require File.expand_path("../../config/environment", __FILE__)
+require "rails/test_help"
+require "minitest/rails"
+require "minitest/reporters" # for Colorized output
+
+# For colorful output!
+Minitest::Reporters.use!(
+ Minitest::Reporters::SpecReporter.new,
+ ENV,
+ Minitest.backtrace_filter
+)
+
+
+# To add Capybara feature tests add `gem "minitest-rails-capybara"`
+# to the test group in the Gemfile and uncomment the following:
+# require "minitest/rails/capybara"
+
+# Uncomment for awesome colorful output
+# require "minitest/pride"
+
+class ActiveSupport::TestCase
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
+ fixtures :all
+ # Add more helper methods to be used by all tests here...
+
+ def setup
+ # Once you have enabled test mode, all requests
+ # to OmniAuth will be short circuited to use the mock authentication hash.
+ # A request to /auth/provider will redirect immediately to /auth/provider/callback.
+ OmniAuth.config.test_mode = true
+ end
+
+ def mock_auth_hash(user)
+ return {
+ provider: user.oauth_provider,
+ uid: user.oauth_uid,
+ info: {
+ email: user.email,
+ nickname: user.username
+ }
+ }
+ end
+
+ def login_test_user
+ user = merchants(:ada)
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(user))
+ get login_path(:github)
+ end
+
+ def logout_test_user
+ OmniAuth.config.mock_auth[:github] = nil
+ get logout_path
+ end
+end
diff --git a/tmp/.keep b/tmp/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/vendor/.keep b/vendor/.keep
new file mode 100644
index 0000000000..e69de29bb2