guillotine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,27 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'sinatra'
4
+
5
+ group :test do
6
+ gem 'rack-test'
7
+ end
8
+
9
+ # Bundler isn't designed to provide optional functionality like this. You're
10
+ # on your own
11
+ #
12
+ #group :riak do
13
+ # gem 'riak-client'
14
+ #end
15
+ #
16
+ #group :sequel do
17
+ # gem 'sequel'
18
+ #end
19
+ #
20
+ #group :active_record do
21
+ # gem 'activerecord'
22
+ #end
23
+ #
24
+ #group :sequel, :active_record do
25
+ # gem 'sqlite3'
26
+ #end
27
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Rick Olson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/Rakefile ADDED
@@ -0,0 +1,135 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/*_test.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Open an irb session preloaded with this library"
56
+ task :console do
57
+ sh "irb -rubygems -r ./lib/#{name}.rb"
58
+ end
59
+
60
+ #############################################################################
61
+ #
62
+ # Custom tasks (add your own tasks here)
63
+ #
64
+ #############################################################################
65
+
66
+
67
+
68
+ #############################################################################
69
+ #
70
+ # Packaging tasks
71
+ #
72
+ #############################################################################
73
+
74
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
75
+ task :release => :build do
76
+ unless `git branch` =~ /^\* master$/
77
+ puts "You must be on the master branch to release!"
78
+ exit!
79
+ end
80
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
81
+ sh "git tag v#{version}"
82
+ sh "git push origin master"
83
+ sh "git push origin v#{version}"
84
+ sh "gem push pkg/#{name}-#{version}.gem"
85
+ end
86
+
87
+ desc "Build #{gem_file} into the pkg directory"
88
+ task :build => :gemspec do
89
+ sh "mkdir -p pkg"
90
+ sh "gem build #{gemspec_file}"
91
+ sh "mv #{gem_file} pkg"
92
+ end
93
+
94
+ desc "Generate #{gemspec_file}"
95
+ task :gemspec => :validate do
96
+ # read spec file and split out manifest section
97
+ spec = File.read(gemspec_file)
98
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
99
+
100
+ # replace name version and date
101
+ replace_header(head, :name)
102
+ replace_header(head, :version)
103
+ replace_header(head, :date)
104
+ #comment this out if your rubyforge_project has a different name
105
+ replace_header(head, :rubyforge_project)
106
+
107
+ # determine file list from git ls-files
108
+ files = `git ls-files`.
109
+ split("\n").
110
+ sort.
111
+ reject { |file| file =~ /^\./ }.
112
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
113
+ map { |file| " #{file}" }.
114
+ join("\n")
115
+
116
+ # piece file back together and write
117
+ manifest = " s.files = %w[\n#{files}\n ]\n"
118
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
119
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
120
+ puts "Updated #{gemspec_file}"
121
+ end
122
+
123
+ desc "Validate #{gemspec_file}"
124
+ task :validate do
125
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
126
+ unless libfiles.empty?
127
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
128
+ exit!
129
+ end
130
+ unless Dir['VERSION*'].empty?
131
+ puts "A `VERSION` file at root level violates Gem best practices."
132
+ exit!
133
+ end
134
+ end
135
+
@@ -0,0 +1,26 @@
1
+ # Sample haproxy config for riak protobuf api
2
+ # Assumes you have 3 riak instances setup like the fast track
3
+ # http://wiki.basho.com/Building-a-Development-Environment.html
4
+ #
5
+ # Adapted from:
6
+ # http://www.mail-archive.com/riak-users@lists.basho.com/msg03502.html
7
+ global
8
+ user technoweenie # set this to your user :)
9
+ group staff
10
+ #daemon
11
+ maxconn 100000
12
+
13
+ defaults
14
+ mode tcp
15
+ balance leastconn
16
+ clitimeout 60000
17
+ srvtimeout 60000
18
+ contimeout 5000
19
+ retries 3
20
+ option redispatch
21
+
22
+ listen riak localhost:8080
23
+ server riak1 localhost:8081 weight 1 maxconn 1000
24
+ server riak2 localhost:8082 weight 1 maxconn 1000
25
+ server riak3 localhost:8083 weight 1 maxconn 1000
26
+
@@ -0,0 +1,73 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'guillotine'
16
+ s.version = '0.0.1'
17
+ s.date = '2011-08-08'
18
+ s.rubyforge_project = 'guillotine'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = "Adaptable private URL shortener"
23
+ s.description = "Adaptable private URL shortener"
24
+
25
+ ## List the primary authors. If there are a bunch of authors, it's probably
26
+ ## better to set the email to an email list or something. If you don't have
27
+ ## a custom homepage, consider using your GitHub URL or the like.
28
+ s.authors = ["Rick Olson"]
29
+ s.email = 'technoweenie@gmail.com'
30
+ s.homepage = 'https://github.com/technoweenie/gitio'
31
+
32
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
33
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
34
+ s.require_paths = %w[lib]
35
+
36
+ ## List your runtime dependencies here. Runtime dependencies are those
37
+ ## that are needed for an end user to actually USE your code.
38
+ s.add_dependency('sinatra', "~> 1.2.6")
39
+
40
+ ## List your development dependencies here. Development dependencies are
41
+ ## those that are only needed during development
42
+ s.add_development_dependency('rack-test')
43
+
44
+ ## Leave this section as-is. It will be automatically generated from the
45
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
46
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
47
+ # = MANIFEST =
48
+ s.files = %w[
49
+ Gemfile
50
+ LICENSE
51
+ Rakefile
52
+ config/haproxy.riak.cfg
53
+ guillotine.gemspec
54
+ lib/guillotine.rb
55
+ lib/guillotine/adapters/active_record_adapter.rb
56
+ lib/guillotine/adapters/memory_adapter.rb
57
+ lib/guillotine/adapters/riak_adapter.rb
58
+ lib/guillotine/adapters/sequel_adapter.rb
59
+ lib/guillotine/app.rb
60
+ test/active_record_adapter_test.rb
61
+ test/app_test.rb
62
+ test/helper.rb
63
+ test/memory_adapter_test.rb
64
+ test/riak_adapter_test.rb
65
+ test/sequel_adapter_test.rb
66
+ ]
67
+ # = MANIFEST =
68
+
69
+ ## Test files will be grabbed from the file list. Make sure the path glob
70
+ ## matches what you actually use.
71
+ s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb/ }
72
+ end
73
+
@@ -0,0 +1,58 @@
1
+ require 'active_record'
2
+
3
+ module Guillotine
4
+ module Adapters
5
+ class ActiveRecordAdapter < Adapter
6
+ class Url < ActiveRecord::Base; end
7
+
8
+ def initialize(config)
9
+ Url.establish_connection config
10
+ end
11
+
12
+ # Public: Stores the shortened version of a URL.
13
+ #
14
+ # url - The String URL to shorten and store.
15
+ # code - Optional String code for the URL.
16
+ #
17
+ # Returns the unique String code for the URL. If the URL is added
18
+ # multiple times, this should return the same code.
19
+ def add(url, code = nil)
20
+ if row = Url.select(:code).where(:url => url).first
21
+ row[:code]
22
+ else
23
+ code ||= shorten url
24
+ begin
25
+ Url.create :url => url, :code => code
26
+ rescue ActiveRecord::RecordNotUnique
27
+ row = Url.select(:url).where(:code => code).first
28
+ existing_url = row && row[:url]
29
+ raise DuplicateCodeError.new(existing_url, url, code)
30
+ end
31
+ code
32
+ end
33
+ end
34
+
35
+ # Public: Retrieves a URL from the code.
36
+ #
37
+ # code - The String code to lookup the URL.
38
+ #
39
+ # Returns the String URL.
40
+ def find(code)
41
+ if row = Url.select(:url).where(:code => code).first
42
+ row[:url]
43
+ end
44
+ end
45
+
46
+ def setup
47
+ conn = Url.connection
48
+ conn.create_table :urls do |t|
49
+ t.string :url
50
+ t.string :code
51
+ end
52
+
53
+ conn.add_index :urls, :url, :unique => true
54
+ conn.add_index :urls, :code, :unique => true
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,41 @@
1
+ module Guillotine
2
+ module Adapters
3
+ # Stores shortened URLs in memory. Totally scales.
4
+ class MemoryAdapter < Adapter
5
+ def initialize
6
+ @hash = {}
7
+ @urls = {}
8
+ end
9
+
10
+ # Public: Stores the shortened version of a URL.
11
+ #
12
+ # url - The String URL to shorten and store.
13
+ # code - Optional String code for the URL.
14
+ #
15
+ # Returns the unique String code for the URL. If the URL is added
16
+ # multiple times, this should return the same code.
17
+ def add(url, code = nil)
18
+ if existing_code = @urls[url]
19
+ existing_code
20
+ else
21
+ code ||= shorten(url)
22
+ if existing_url = @hash[code]
23
+ raise DuplicateCodeError.new(existing_url, url, code) if url != existing_url
24
+ end
25
+ @hash[code] = url
26
+ @urls[url] = code
27
+ code
28
+ end
29
+ end
30
+
31
+ # Public: Retrieves a URL from the code.
32
+ #
33
+ # code - The String code to lookup the URL.
34
+ #
35
+ # Returns the String URL.
36
+ def find(code)
37
+ @hash[code]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ require 'digest/sha1'
2
+
3
+ module Guillotine
4
+ module Adapters
5
+ # Stores shortened URLs in Riak. Totally scales.
6
+ class RiakAdapter < Adapter
7
+ def initialize(bucket)
8
+ @bucket = bucket
9
+ @client = bucket.client
10
+ end
11
+
12
+ # Public: Stores the shortened version of a URL.
13
+ #
14
+ # url - The String URL to shorten and store.
15
+ # code - Optional String code for the URL.
16
+ #
17
+ # Returns the unique String code for the URL. If the URL is added
18
+ # multiple times, this should return the same code.
19
+ def add(url, code = nil)
20
+ sha = Digest::SHA1.hexdigest url
21
+ url_obj = @bucket.get_or_new sha, :r => 1
22
+ url_obj.data || begin
23
+ code ||= shorten url
24
+ code_obj = @bucket.get_or_new code
25
+ if existing_url = code_obj.data # key exists
26
+ raise DuplicateCodeError.new(existing_url, url, code) if existing_url != url
27
+ end
28
+ code_obj.content_type = url_obj.content_type = 'text/plain'
29
+ code_obj.data = url
30
+ url_obj.data = code
31
+ code_obj.store
32
+ url_obj.store
33
+ code
34
+ end
35
+ end
36
+
37
+ # Public: Retrieves a URL from the code.
38
+ #
39
+ # code - The String code to lookup the URL.
40
+ #
41
+ # Returns the String URL.
42
+ def find(code)
43
+ if obj = @bucket.get(code, :r => 1)
44
+ obj.data
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,56 @@
1
+ module Guillotine
2
+ module Adapters
3
+ class SequelAdapter < Adapter
4
+ def initialize(db)
5
+ @db = db
6
+ @table = @db[:urls]
7
+ end
8
+
9
+ # Public: Stores the shortened version of a URL.
10
+ #
11
+ # url - The String URL to shorten and store.
12
+ # code - Optional String code for the URL.
13
+ #
14
+ # Returns the unique String code for the URL. If the URL is added
15
+ # multiple times, this should return the same code.
16
+ def add(url, code = nil)
17
+ if row = @table.select(:code).where(:url => url).first
18
+ row[:code]
19
+ else
20
+ code ||= shorten url
21
+ begin
22
+ @table << {:url => url, :code => code}
23
+ rescue Sequel::DatabaseError
24
+ if existing_url = @table.select(:url).where(:code => code).first
25
+ raise DuplicateCodeError.new(existing_url, url, code)
26
+ else
27
+ raise
28
+ end
29
+ end
30
+ code
31
+ end
32
+ end
33
+
34
+ # Public: Retrieves a URL from the code.
35
+ #
36
+ # code - The String code to lookup the URL.
37
+ #
38
+ # Returns the String URL.
39
+ def find(code)
40
+ if row = @table.select(:url).where(:code => code).first
41
+ row[:url]
42
+ end
43
+ end
44
+
45
+ def setup
46
+ @db.create_table :urls do
47
+ string :url
48
+ string :code
49
+
50
+ unique :url
51
+ unique :code
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ require 'sinatra/base'
2
+
3
+ module Guillotine
4
+ class App < Sinatra::Base
5
+ get "/:code" do
6
+ code = params[:code]
7
+ if url = settings.db.find(code)
8
+ redirect url
9
+ else
10
+ halt 404, "No url found for #{code}"
11
+ end
12
+ end
13
+
14
+ post "/" do
15
+ url = params[:url]
16
+ code = params[:code]
17
+ begin
18
+ if code = settings.db.add(url, code)
19
+ redirect code
20
+ else
21
+ halt 422, "Unable to shorten #{url}"
22
+ end
23
+ rescue Guillotine::DuplicateCodeError => err
24
+ halt 422, err.to_s
25
+ end
26
+ end
27
+ end
28
+ end
data/lib/guillotine.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'base64'
2
+ require 'digest/md5'
3
+
4
+ module Guillotine
5
+ VERSION = "0.0.1"
6
+
7
+ dir = File.expand_path '../guillotine', __FILE__
8
+ autoload :App, "#{dir}/app"
9
+
10
+ class DuplicateCodeError < StandardError
11
+ attr_reader :existing_url, :new_url, :code
12
+
13
+ def initialize(existing_url, new_url, code)
14
+ @existing_url = existing_url
15
+ @new_url = new_url
16
+ @code = code
17
+ super "#{@new_url.inspect} was supposed to be shortened to #{@code.inspect}, but #{@existing_url.inspect} already is!"
18
+ end
19
+ end
20
+
21
+ module Adapters
22
+ dir = File.expand_path '../guillotine/adapters', __FILE__
23
+ autoload :MemoryAdapter, "#{dir}/memory_adapter"
24
+ autoload :SequelAdapter, "#{dir}/sequel_adapter"
25
+ autoload :RiakAdapter, "#{dir}/riak_adapter"
26
+ autoload :ActiveRecordAdapter, "#{dir}/active_record_adapter"
27
+
28
+ # Adapters handle the storage and retrieval of URLs in the system. You can
29
+ # use whatever you want, as long as it implements the #add and #find
30
+ # methods. See MemoryAdapter for a simple solution.
31
+ class Adapter
32
+ # Public: Shortens a given URL to a short code.
33
+ #
34
+ # 1) MD5 hash the URL to the hexdigest
35
+ # 2) Convert it to a Bignum
36
+ # 3) Pack it into a bitstring as a big-endian int
37
+ # 4) base64-encode the bitstring, remove the trailing junk
38
+ #
39
+ # url - String URL to shorten.
40
+ #
41
+ # Returns a unique String code for the URL.
42
+ def shorten(url)
43
+ Base64.urlsafe_encode64([Digest::MD5.hexdigest(url).to_i(16)].pack("N")).sub(/==\n?$/, '')
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'active_record'
5
+ class ActiveRecordAdapterTest < Guillotine::TestCase
6
+ ADAPTER = Guillotine::Adapters::ActiveRecordAdapter.new :adapter => 'sqlite3', :database => ':memory:'
7
+ ADAPTER.setup
8
+
9
+ def setup
10
+ @db = ADAPTER
11
+ end
12
+
13
+ def test_adding_a_link_returns_code
14
+ code = @db.add 'abc'
15
+ assert_equal 'abc', @db.find(code)
16
+ end
17
+
18
+ def test_adding_duplicate_link_returns_same_code
19
+ code = @db.add 'abc'
20
+ assert_equal code, @db.add('abc')
21
+ end
22
+
23
+ def test_adds_url_with_custom_code
24
+ assert_equal 'code', @db.add('def', 'code')
25
+ assert_equal 'def', @db.find('code')
26
+ end
27
+
28
+ def test_clashing_urls_raises_error
29
+ code = @db.add '123'
30
+ assert_raises Guillotine::DuplicateCodeError do
31
+ code = @db.add '456', code
32
+ end
33
+ end
34
+ end
35
+ rescue LoadError
36
+ puts "skipping ActiveRecord tests: #{$!}"
37
+ end
38
+
data/test/app_test.rb ADDED
@@ -0,0 +1,49 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class AppTest < Guillotine::TestCase
4
+ ADAPTER = Guillotine::Adapters::MemoryAdapter.new
5
+ Guillotine::App.set :db, ADAPTER
6
+
7
+ include Rack::Test::Methods
8
+
9
+ def test_adding_a_link_returns_code
10
+ url = 'http://github.com'
11
+ post '/', :url => url
12
+ assert code_url = last_response.headers['Location']
13
+ code = code_url.gsub(/.*\//, '')
14
+
15
+ get "/#{code}"
16
+ assert_equal 302, last_response.status
17
+ assert_equal url, last_response.headers['Location']
18
+ end
19
+
20
+ def test_adding_duplicate_link_returns_same_code
21
+ url = 'http://github.com'
22
+ code = ADAPTER.add url
23
+
24
+ post '/', :url => url
25
+ assert code_url = last_response.headers['Location']
26
+ assert_equal code, code_url.gsub(/.*\//, '')
27
+ end
28
+
29
+ def test_adds_url_with_custom_code
30
+ url = 'http://github.com/abc'
31
+ post '/', :url => url, :code => 'code'
32
+ assert code_url = last_response.headers['Location']
33
+ assert_match /\/code$/, code_url
34
+
35
+ get "/code"
36
+ assert_equal 302, last_response.status
37
+ assert_equal url, last_response.headers['Location']
38
+ end
39
+
40
+ def test_clashing_urls_raises_error
41
+ code = ADAPTER.add '123'
42
+ post '/', :url => '456', :code => code
43
+ assert_equal 422, last_response.status
44
+ end
45
+
46
+ def app
47
+ Guillotine::App
48
+ end
49
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+ require File.expand_path('../../lib/guillotine', __FILE__)
3
+ require 'rack/test'
4
+
5
+ class Guillotine::TestCase < Test::Unit::TestCase
6
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class MemoryAdapterTest < Guillotine::TestCase
4
+ def setup
5
+ @db = Guillotine::Adapters::MemoryAdapter.new
6
+ end
7
+
8
+ def test_adding_a_link_returns_code
9
+ code = @db.add 'abc'
10
+ assert_equal 'abc', @db.find(code)
11
+ end
12
+
13
+ def test_adding_duplicate_link_returns_same_code
14
+ code = @db.add 'abc'
15
+ assert_equal code, @db.add('abc')
16
+ end
17
+
18
+ def test_adds_url_with_custom_code
19
+ assert_equal 'code', @db.add('def', 'code')
20
+ assert_equal 'def', @db.find('code')
21
+ end
22
+
23
+ def test_clashing_urls_raises_error
24
+ code = @db.add 'abc'
25
+ assert_raises Guillotine::DuplicateCodeError do
26
+ @db.add 'def', code
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'riak/client'
5
+
6
+ # Assumes a local dev install of riak is setup
7
+ #
8
+ # http://wiki.basho.com/Building-a-Development-Environment.html
9
+ #
10
+ # Riak should be accessible from:
11
+ #
12
+ # http://localhost:8091/riak/guillotine-test
13
+ class RiakAdapterTest < Guillotine::TestCase
14
+ BUCKET = Riak::Client.new(:http_port => 8091)['guillotine-test']
15
+ ADAPTER = Guillotine::Adapters::RiakAdapter.new BUCKET
16
+
17
+ def setup
18
+ @db = ADAPTER
19
+ end
20
+
21
+ def test_adding_a_link_returns_code
22
+ code = @db.add 'abc'
23
+ assert_equal 'abc', @db.find(code)
24
+
25
+ BUCKET.delete Digest::SHA1.hexdigest('abc')
26
+ BUCKET.delete code
27
+ end
28
+
29
+ def test_adding_duplicate_link_returns_same_code
30
+ code = @db.add 'abc'
31
+ assert_equal code, @db.add('abc')
32
+
33
+ BUCKET.delete Digest::SHA1.hexdigest('abc')
34
+ BUCKET.delete code
35
+ end
36
+
37
+ def test_adds_url_with_custom_code
38
+ assert_equal 'code', @db.add('def', 'code')
39
+ assert_equal 'def', @db.find('code')
40
+
41
+ BUCKET.delete Digest::SHA1.hexdigest('def')
42
+ BUCKET.delete 'code'
43
+ end
44
+
45
+ def test_clashing_urls_raises_error
46
+ code = @db.add 'abc'
47
+ assert_raises Guillotine::DuplicateCodeError do
48
+ @db.add 'def', code
49
+ end
50
+
51
+ BUCKET.delete Digest::SHA1.hexdigest('abc')
52
+ BUCKET.delete Digest::SHA1.hexdigest('def')
53
+ BUCKET.delete code
54
+ end
55
+ end
56
+ rescue LoadError
57
+ puts "Skipping Riak tests: #{$!}"
58
+ end
59
+
@@ -0,0 +1,38 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'sequel'
5
+ class SequelAdapterTest < Guillotine::TestCase
6
+ ADAPTER = Guillotine::Adapters::SequelAdapter.new Sequel.sqlite
7
+ ADAPTER.setup
8
+
9
+ def setup
10
+ @db = ADAPTER
11
+ end
12
+
13
+ def test_adding_a_link_returns_code
14
+ code = @db.add 'abc'
15
+ assert_equal 'abc', @db.find(code)
16
+ end
17
+
18
+ def test_adding_duplicate_link_returns_same_code
19
+ code = @db.add 'abc'
20
+ assert_equal code, @db.add('abc')
21
+ end
22
+
23
+ def test_adds_url_with_custom_code
24
+ assert_equal 'code', @db.add('def', 'code')
25
+ assert_equal 'def', @db.find('code')
26
+ end
27
+
28
+ def test_clashing_urls_raises_error
29
+ code = @db.add '123'
30
+ assert_raises Guillotine::DuplicateCodeError do
31
+ code = @db.add '456', code
32
+ end
33
+ end
34
+ end
35
+ rescue LoadError
36
+ puts "Skipping sequel tests"
37
+ end
38
+
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: guillotine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rick Olson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-08 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: &70305960198340 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.2.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70305960198340
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack-test
27
+ requirement: &70305960197960 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70305960197960
36
+ description: Adaptable private URL shortener
37
+ email: technoweenie@gmail.com
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - Gemfile
43
+ - LICENSE
44
+ - Rakefile
45
+ - config/haproxy.riak.cfg
46
+ - guillotine.gemspec
47
+ - lib/guillotine.rb
48
+ - lib/guillotine/adapters/active_record_adapter.rb
49
+ - lib/guillotine/adapters/memory_adapter.rb
50
+ - lib/guillotine/adapters/riak_adapter.rb
51
+ - lib/guillotine/adapters/sequel_adapter.rb
52
+ - lib/guillotine/app.rb
53
+ - test/active_record_adapter_test.rb
54
+ - test/app_test.rb
55
+ - test/helper.rb
56
+ - test/memory_adapter_test.rb
57
+ - test/riak_adapter_test.rb
58
+ - test/sequel_adapter_test.rb
59
+ homepage: https://github.com/technoweenie/gitio
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project: guillotine
79
+ rubygems_version: 1.8.6
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: Adaptable private URL shortener
83
+ test_files:
84
+ - test/active_record_adapter_test.rb
85
+ - test/app_test.rb
86
+ - test/memory_adapter_test.rb
87
+ - test/riak_adapter_test.rb
88
+ - test/sequel_adapter_test.rb