diff --git a/.gitignore b/.gitignore
index b9505882..abcadb97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ tests/coverage
config.yml
.DS_Store
domains
+public/sites_test
diff --git a/Gemfile b/Gemfile
index 36459aad..fa2df6b0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -52,6 +52,10 @@ group :test do
gem 'webmock'
gem 'mocha', require: nil
gem 'rake', require: nil
+ gem 'poltergeist'
+ gem 'phantomjs', require: 'phantomjs/poltergeist'
+ gem 'capybara'
+ gem 'capybara_minitest_spec'
platform :mri do
gem 'simplecov', require: nil
diff --git a/Gemfile.lock b/Gemfile.lock
index d56de687..41ed276c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -6,10 +6,20 @@ GEM
ansi (1.4.3)
bcrypt (3.1.7)
builder (3.2.2)
+ capybara (2.2.1)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ xpath (~> 2.0)
+ capybara_minitest_spec (1.0.1)
+ capybara (>= 2)
+ minitest (>= 2)
celluloid (0.15.2)
timers (~> 1.1.0)
childprocess (0.5.2)
ffi (~> 1.0, >= 1.0.11)
+ cliver (0.3.2)
coderay (1.1.0)
columnize (0.3.6)
connection_pool (2.0.0)
@@ -39,6 +49,7 @@ GEM
metaclass (0.0.4)
method_source (0.8.2)
mime-types (1.25.1)
+ mini_portile (0.5.3)
minitest (5.3.1)
minitest-reporters (1.0.2)
ansi
@@ -48,7 +59,15 @@ GEM
mocha (1.0.0)
metaclass (~> 0.0.1)
multi_json (1.9.2)
+ nokogiri (1.6.1)
+ mini_portile (~> 0.5.0)
pg (0.17.1)
+ phantomjs (1.9.7.0)
+ poltergeist (1.5.0)
+ capybara (~> 2.1)
+ cliver (~> 0.3.1)
+ multi_json (~> 1.0)
+ websocket-driver (>= 0.2.0)
polyglot (0.3.4)
powerbar (1.0.11)
ansi (~> 1.4.0)
@@ -129,6 +148,9 @@ GEM
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket (1.0.7)
+ websocket-driver (0.3.2)
+ xpath (2.0.0)
+ nokogiri (~> 1.3)
PLATFORMS
ruby
@@ -136,6 +158,8 @@ PLATFORMS
DEPENDENCIES
ago
bcrypt
+ capybara
+ capybara_minitest_spec
fabrication
faker
hiredis
@@ -148,6 +172,8 @@ DEPENDENCIES
minitest-reporters
mocha
pg
+ phantomjs
+ poltergeist
pry
pry-debugger
puma
diff --git a/Rakefile b/Rakefile
index 6d1f2105..2d1ad6c3 100644
--- a/Rakefile
+++ b/Rakefile
@@ -7,7 +7,7 @@ end
desc "Run all tests"
Rake::TestTask.new do |t|
t.libs << "spec"
- t.test_files = FileList['tests/*_test.rb']
+ t.test_files = FileList['tests/*_tests.rb']
t.verbose = true
end
diff --git a/app.rb b/app.rb
index d185285a..406185a2 100644
--- a/app.rb
+++ b/app.rb
@@ -103,12 +103,12 @@ end
get '/blog' do
# expires 500, :public, :must_revalidate
- return File.read File.join(DIR_ROOT, 'public', 'sites', 'blog', 'index.html')
+ return File.read File.join(Sites::SITE_FILES_ROOT, 'blog', 'index.html')
end
get '/blog/:article' do |article|
# expires 500, :public, :must_revalidate
- path = File.join DIR_ROOT, 'public', 'sites', 'blog', "#{article}.html"
+ path = File.join Sites::SITE_FILES_ROOT, 'blog', "#{article}.html"
pass if !File.exist?(path)
File.read path
end
@@ -137,9 +137,16 @@ end
post '/create' do
dashboard_if_signed_in
- @site = Site.new username: params[:username], password: params[:password], email: params[:email], new_tags: params[:tags], is_nsfw: params[:is_nsfw], ip: request.ip
+ @site = Site.new(
+ username: params[:username],
+ password: params[:password],
+ email: params[:email],
+ new_tags: params[:tags],
+ is_nsfw: params[:is_nsfw],
+ ip: request.ip
+ )
- recaptcha_is_valid = recaptcha_valid?
+ recaptcha_is_valid = ENV['RACK_ENV'] == 'test' || recaptcha_valid?
if @site.valid? && recaptcha_is_valid
@@ -148,7 +155,7 @@ post '/create' do
DB.transaction {
@site.save
- FileUtils.mkdir base_path
+ FileUtils.mkdir_p base_path
File.write File.join(base_path, 'index.html'), slim(:'templates/index', pretty: true, layout: false)
File.write File.join(base_path, 'not_found.html'), slim(:'templates/not_found', pretty: true, layout: false)
@@ -661,7 +668,7 @@ def current_site
end
def site_base_path(subname)
- File.join settings.public_folder, 'sites', subname
+ File.join Site::SITE_FILES_ROOT, subname
end
def site_file_path(filename)
diff --git a/migrations/007_add_hits_counter.rb b/migrations/007_add_hits_counter.rb
index 9313ab17..db050953 100644
--- a/migrations/007_add_hits_counter.rb
+++ b/migrations/007_add_hits_counter.rb
@@ -4,6 +4,6 @@ Sequel.migration do
}
down {
- DB.add_column :sites, :hits
+ DB.drop_column :sites, :hits
}
end
\ No newline at end of file
diff --git a/migrations/011_add_admin_flag.rb b/migrations/011_add_admin_flag.rb
index 2ccb9cde..c0e2ad4a 100644
--- a/migrations/011_add_admin_flag.rb
+++ b/migrations/011_add_admin_flag.rb
@@ -4,6 +4,6 @@ Sequel.migration do
}
down {
- DB.add_column :sites, :is_admin
+ DB.drop_column :sites, :is_admin
}
end
\ No newline at end of file
diff --git a/migrations/012_add_banned_flag.rb b/migrations/012_add_banned_flag.rb
index 763a8f0a..37165eaa 100644
--- a/migrations/012_add_banned_flag.rb
+++ b/migrations/012_add_banned_flag.rb
@@ -4,6 +4,6 @@ Sequel.migration do
}
down {
- DB.add_column :sites, :is_banned
+ DB.drop_column :sites, :is_banned
}
end
\ No newline at end of file
diff --git a/migrations/013_add_ip_address_of_creator.rb b/migrations/013_add_ip_address_of_creator.rb
index 0bc87f17..4100361c 100644
--- a/migrations/013_add_ip_address_of_creator.rb
+++ b/migrations/013_add_ip_address_of_creator.rb
@@ -4,6 +4,6 @@ Sequel.migration do
}
down {
- DB.add_column :sites, :ip
+ DB.drop_column :sites, :ip
}
end
\ No newline at end of file
diff --git a/migrations/014_add_password_reset_token.rb b/migrations/014_add_password_reset_token.rb
index d4d6689f..c4f0a20f 100644
--- a/migrations/014_add_password_reset_token.rb
+++ b/migrations/014_add_password_reset_token.rb
@@ -6,6 +6,5 @@ Sequel.migration do
down {
DB.drop_column :sites, :password_reset_token
- DB.drop_index :sites, :password_reset_token
}
end
\ No newline at end of file
diff --git a/migrations/015_mark_site_changed.rb b/migrations/015_mark_site_changed.rb
index 38755d20..3f754d8a 100644
--- a/migrations/015_mark_site_changed.rb
+++ b/migrations/015_mark_site_changed.rb
@@ -6,6 +6,5 @@ Sequel.migration do
down {
DB.drop_column :sites, :site_changed
- DB.drop_index :sites, :site_changed
}
end
\ No newline at end of file
diff --git a/migrations/016_site_updated_count.rb b/migrations/016_site_updated_count.rb
index 4ba7c0bb..52dfbcdc 100644
--- a/migrations/016_site_updated_count.rb
+++ b/migrations/016_site_updated_count.rb
@@ -6,6 +6,5 @@ Sequel.migration do
down {
DB.drop_column :sites, :changed_count
- DB.drop_index :sites, :changed_count
}
end
\ No newline at end of file
diff --git a/models/site.rb b/models/site.rb
index 4deb322e..248f33b1 100644
--- a/models/site.rb
+++ b/models/site.rb
@@ -1,11 +1,38 @@
class Site < Sequel::Model
# We might need to include fonts in here..
- VALID_MIME_TYPES = ['text/plain', 'text/html', 'text/css', 'application/javascript', 'image/png', 'image/jpeg', 'image/gif', 'image/svg+xml', 'application/vnd.ms-fontobject', 'application/x-font-ttf', 'application/octet-stream', 'text/csv', 'text/tsv', 'text/cache-manifest', 'image/x-icon', 'application/pdf', 'application/pgp-keys', 'text/xml', 'application/xml', 'audio/midi']
- VALID_EXTENSIONS = %w{ html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json geojson csv tsv mf ico pdf asc key pgp xml mid midi }
- #USERNAME_SHITLIST = %w{ payment secure login signin www ww web } # I thought they were funny personally, but everybody is freaking out so..
+ VALID_MIME_TYPES = %w{
+ text/plain
+ text/html
+ text/css
+ application/javascript
+ image/png
+ image/jpeg
+ image/gif
+ image/svg+xml
+ application/vnd.ms-fontobject
+ application/x-font-ttf
+ application/octet-stream
+ text/csv
+ text/tsv
+ text/cache-manifest
+ image/x-icon
+ application/pdf
+ application/pgp-keys
+ text/xml
+ application/xml
+ audio/midi
+ }
+ VALID_EXTENSIONS = %w{
+ html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json
+ geojson csv tsv mf ico pdf asc key pgp xml mid midi
+ }
MAX_SPACE = (5242880*2) # 10MB
MINIMUM_PASSWORD_LENGTH = 5
BAD_USERNAME_REGEX = /[^\w-]/i
+ VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123
+
+ SITE_FILES_ROOT = File.join(DIR_ROOT, 'public', (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites'))
+
many_to_one :server
many_to_many :tags
@@ -77,13 +104,15 @@ class Site < Sequel::Model
errors.add :over_capacity, 'We are currently at capacity, and cannot create your home page. We will fix this shortly. Please come back later and try again, our apologies.'
end
- if values[:username].nil? || values[:username].empty? || values[:username].match(BAD_USERNAME_REGEX)
- errors.add :username, 'A valid username is required.'
+ if !values[:username].match(VALID_HOSTNAME)
+ errors.add :username, 'A valid user/site name is required.'
+ end
+
+ if values[:username].length > 32
+ errors.add :username, 'User/site name cannot exceed 32 characters.'
end
- # Check for existing user
-
-
+ # Check for existing user
user = self.class.select(:id, :username).filter(username: values[:username]).first
if user
@@ -109,7 +138,7 @@ class Site < Sequel::Model
end
def file_path
- File.join DIR_ROOT, 'public', 'sites', username
+ File.join SITE_FILES_ROOT, username
end
def file_list
diff --git a/tests/acceptance_tests.rb b/tests/acceptance_tests.rb
new file mode 100644
index 00000000..65815cd5
--- /dev/null
+++ b/tests/acceptance_tests.rb
@@ -0,0 +1,144 @@
+require_relative './environment'
+
+Capybara.app = Sinatra::Application
+
+def teardown
+ Capybara.reset_sessions!
+ Capybara.use_default_driver
+end
+
+describe 'index' do
+ include Capybara::DSL
+ it 'goes to signup' do
+ visit '/'
+ click_button 'Create My Website'
+ page.must_have_content('Create a New Home Page')
+ end
+end
+
+describe 'signup' do
+ include Capybara::DSL
+
+ def fill_in_valid
+ @site = Fabricate.attributes_for(:site)
+ fill_in 'username', with: @site[:username]
+ fill_in 'password', with: @site[:password]
+ end
+
+ def visit_signup
+ visit '/'
+ click_button 'Create My Website'
+ end
+
+ before do
+ Capybara.reset_sessions!
+ visit_signup
+ end
+
+ it 'succeeds with valid data' do
+ fill_in_valid
+ click_button 'Create Home Page'
+ page.must_have_content 'Your Website'
+ assert_equal(
+ true,
+ File.exist?(File.join(Site::SITE_FILES_ROOT, @site[:username], 'index.html'))
+ )
+ end
+
+ it 'fails to create for existing site' do
+ fill_in_valid
+ click_button 'Create Home Page'
+ page.must_have_content 'Your Website'
+ Capybara.reset_sessions!
+ visit_signup
+ fill_in 'username', with: @site[:username]
+ fill_in 'password', with: @site[:password]
+ click_button 'Create Home Page'
+ page.must_have_content 'already taken'
+ end
+
+ it 'fails with missing password' do
+ fill_in_valid
+ fill_in 'password', with: ''
+ click_button 'Create Home Page'
+ page.must_have_content 'Password must be at least 5 characters'
+ end
+
+ it 'fails with short password' do
+ fill_in_valid
+ fill_in 'password', with: 'derp'
+ click_button 'Create Home Page'
+ page.must_have_content 'Password must be at least 5 characters'
+ end
+
+ it 'fails with invalid hostname for username' do
+ fill_in_valid
+ fill_in 'username', with: '|\|0p|E'
+ click_button 'Create Home Page'
+ page.current_path.must_equal '/create'
+ page.must_have_content 'A valid user/site name is required'
+ fill_in 'username', with: 'nope-'
+ click_button 'Create Home Page'
+ page.must_have_content 'A valid user/site name is required'
+ fill_in 'username', with: '-nope'
+ click_button 'Create Home Page'
+ page.must_have_content 'A valid user/site name is required'
+ end
+
+ it 'fails with username greater than 32 characters' do
+ fill_in_valid
+ fill_in 'username', with: SecureRandom.hex+'1'
+ click_button 'Create Home Page'
+ page.current_path.must_equal '/create'
+ page.must_have_content 'cannot exceed 32 characters'
+ end
+end
+
+describe 'signin' do
+ include Capybara::DSL
+
+ def fill_in_valid
+ site = Fabricate.attributes_for :site
+ fill_in 'username', with: site[:username]
+ fill_in 'password', with: site[:password]
+ end
+
+ before do
+ Capybara.reset_sessions!
+ end
+
+ it 'fails for invalid login' do
+ visit '/'
+ click_link 'Sign In'
+ page.must_have_content 'Welcome back'
+ fill_in_valid
+ click_button 'Sign in'
+ page.must_have_content 'Invalid login'
+ end
+
+ it 'fails for missing login' do
+ visit '/'
+ click_link 'Sign In'
+ auth = {username: SecureRandom.hex, password: Faker::Internet.password}
+ fill_in 'username', with: auth[:username]
+ fill_in 'password', with: auth[:password]
+ click_button 'Sign in'
+ page.must_have_content 'Invalid login'
+ end
+
+ it 'logs in with proper credentials' do
+ visit '/'
+ click_button 'Create My Website'
+ site = Fabricate.attributes_for(:site)
+ fill_in 'username', with: site[:username]
+ fill_in 'password', with: site[:password]
+ click_button 'Create Home Page'
+ Capybara.reset_sessions!
+ visit '/'
+ click_link 'Sign In'
+ fill_in 'username', with: site[:username]
+ fill_in 'password', with: site[:password]
+ click_button 'Sign in'
+ page.must_have_content 'Your Website'
+ end
+end
\ No newline at end of file
diff --git a/tests/app_test.rb b/tests/app_test.rb
deleted file mode 100644
index 8d9b8b2d..00000000
--- a/tests/app_test.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require_relative './environment'
-
-include Rack::Test::Methods
-
-def app; App end
-def status; last_response.status end
-def headers; last_response.headers end
-def body; last_response.body end
-
-describe 'index' do
- it 'loads' do
- get '/'
- status.must_equal 200
- end
-end
-
-describe 'signin' do
- it 'fails for missing login' do
- post '/signin', username: 'derpie', password: 'lol'
- fail_signin
- end
-
- it 'fails for bad password' do
- @site = Fabricate :site
- post '/signin', username: @site.username, password: 'derp'
- fail_signin
- end
-
- it 'fails for no input' do
- post '/signin'
- fail_signin
- end
-
- it 'succeeds for valid input' do
- password = '1tw0rkz'
- @account = Fabricate :account, password: password
- post '/accounts/signin', username: @account.email, password: password
- headers['Location'].must_equal 'http://example.org/dashboard'
- mock_dashboard_calls @account.email
- get '/dashboard'
- body.must_match /Dashboard/
- end
-end
-
-describe 'account creation' do
- it 'fails for no input' do
- post '/accounts/create'
- status.must_equal 200
- body.must_match /There were some errors.+Valid email address is required.+Password must be/
- end
-
- it 'fails with invalid email' do
- post '/accounts/create', email: 'derplol'
- status.must_equal 200
- body.must_match /errors.+valid email/i
- end
-
- it 'fails with invalid password' do
- post '/accounts/create', 'email@example.com', password: 'sdd'
- status.must_equal 200
- body.must_match /errors.+Password must be at least #{Account::MINIMUM_PASSWORD_LENGTH} characters/i
- end
-
- it 'succeeds with valid info' do
- account_attributes = Fabricate.attributes_for :account
-
- mock_dashboard_calls account_attributes[:email]
-
- post '/accounts/create', account_attributes
- status.must_equal 302
- headers['Location'].must_equal 'http://example.org/dashboard'
-
- get '/dashboard'
- body.must_match /Dashboard/
- end
-end
-
-describe 'temporary account login' do
-end
-
-def fail_signin
- headers['Location'].must_equal 'http://example.org/'
- get '/'
- body.must_match /invalid signin/i
-end
-
-def api_url
- uri = Addressable::URI.parse $config['bitcoind_rpchost'] ? $config['bitcoind_rpchost'] : 'http://localhost'
- uri.port = 8332 if uri.port.nil?
- uri.user = $config['bitcoind_rpcuser'] if uri.user.nil?
- uri.password = $config['bitcoind_rpcpassword'] if uri.password.nil?
- "#{uri.to_s}/"
-end
\ No newline at end of file
diff --git a/tests/environment.rb b/tests/environment.rb
index 141ded8e..e888352c 100644
--- a/tests/environment.rb
+++ b/tests/environment.rb
@@ -20,7 +20,7 @@ Bundler.require :test
require 'minitest/autorun'
require 'sidekiq/testing/inline'
-Account.bcrypt_cost = BCrypt::Engine::MIN_COST
+Site.bcrypt_cost = BCrypt::Engine::MIN_COST
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
@@ -29,8 +29,11 @@ Sequel.extension :migration
Sequel::Migrator.apply DB, './migrations', 0
Sequel::Migrator.apply DB, './migrations'
+Server.create ip: '127.0.0.1', slots_available: 999999
Fabrication.configure do |config|
config.fabricator_path = 'tests/fabricators'
config.path_prefix = DIR_ROOT
-end
\ No newline at end of file
+end
+
+I18n.enforce_available_locales = true
\ No newline at end of file
diff --git a/tests/fabricators/site_fabricator.rb b/tests/fabricators/site_fabricator.rb
index db81e5b7..6f70e455 100644
--- a/tests/fabricators/site_fabricator.rb
+++ b/tests/fabricators/site_fabricator.rb
@@ -1,4 +1,4 @@
Fabricator(:site) do
- username { Faker::Internet.email }
+ username { SecureRandom.hex }
password { 'abcde' }
end
\ No newline at end of file
diff --git a/views/new.slim b/views/new.slim
index 878ebb78..4ea6758e 100644
--- a/views/new.slim
+++ b/views/new.slim
@@ -19,35 +19,39 @@ javascript:
h2.txt-Center Create a New Home Page
.col.col-50 style="margin:0 auto; float:none"
- p.tiny First, enter a username. This will also be used as your site path. Do not forget this, it will be used to sign in to and manage your home page. It cannot contain spaces, and can only use the following characters: a-z A-Z 0-9 _ -
+ hr
+ p.tiny First, enter a username. This will also be used as your site name. Do not forget this, it will be used to sign in to and manage your home page. It can only contain letters, numbers, underscores and hyphens, and can only be 32 characters long.
br
h5 Username
p.tiny .neocities.org
-
+ hr
p.tiny Next, enter a password. This will be used to allow you to login. Minimum 5 characters. If you don't make it a good password, Dade Murphy from the movie Hackers will come in and steal your "garbage files".
- hr
+
h5 Password
input class="input-Area" name="password" type="password"
br
+ hr
p.tiny Now you can enter an e-mail address. Your e-mail address is private and we will not show it to anyone for any reason. You don't have to provide one, but we will not be able to reset your password without it, so don't lose your username and password if you leave this blank!
- hr
+
h5 Email
input class="input-Area" name="email" type="email" placeholder="youremail@example.com" value="#{@site.email}"
br
+ hr
p.tiny You can optionally enter some tags! Tags will allow others to find your site based on your interests, or your site's theme. Separate multiple tags with commas. Don't think too hard about this, you can change them later. You can have a maximum of ten tags, and there is a two word per tag maximum (extra words in a tag will be removed).
- hr
h5 Tags
p: input class="input-Area" name="tags" type="text" style="width: 400px; max-width:100%" placeholder="pokemon, video games, bulbasaur" value="#{params[:tags]}" autocapitalize="off" autocorrect="off"
+ hr
input name="is_nsfw" type="hidden" value="false"
- p: strong If your page will contain objectionable (adult) content, check this box:
+ p If your page will contain objectionable (adult) content, check this box:
+ hr
p.tiny Last thing! Enter these two words correctly (with spaces) so we know you're not a robot (don't worry robots, we still love you).
div style="background:#fff; width:100%; overflow:auto"
== recaptcha_tag :challenge, ssl: true