TextTractor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +66 -0
  5. data/Rakefile +6 -0
  6. data/assets/images/blankpad.png +0 -0
  7. data/assets/images/stale.png +0 -0
  8. data/assets/images/translated.png +0 -0
  9. data/assets/images/untranslated.png +0 -0
  10. data/assets/js/application.js +38 -0
  11. data/assets/js/jquery.js +16 -0
  12. data/assets/js/jquery.pjax.js +188 -0
  13. data/config.ru +20 -0
  14. data/lib/text_tractor.rb +31 -0
  15. data/lib/text_tractor/api_server.rb +93 -0
  16. data/lib/text_tractor/base.rb +25 -0
  17. data/lib/text_tractor/config.rb +48 -0
  18. data/lib/text_tractor/phrase.rb +67 -0
  19. data/lib/text_tractor/projects.rb +202 -0
  20. data/lib/text_tractor/ui_server.rb +152 -0
  21. data/lib/text_tractor/users.rb +49 -0
  22. data/lib/text_tractor/users_spec.rb +10 -0
  23. data/lib/text_tractor/version.rb +3 -0
  24. data/spec/api_server_spec.rb +224 -0
  25. data/spec/config_spec.rb +56 -0
  26. data/spec/phrase_spec.rb +71 -0
  27. data/spec/project_spec.rb +292 -0
  28. data/spec/spec_helper.rb +51 -0
  29. data/spec/ui_server/authentication_spec.rb +60 -0
  30. data/spec/ui_server/project_management_spec.rb +103 -0
  31. data/spec/ui_server/project_viewing_spec.rb +137 -0
  32. data/spec/ui_server_spec.rb +6 -0
  33. data/spec/users_spec.rb +123 -0
  34. data/text_tractor.gemspec +33 -0
  35. data/views/blurbs/_blurb.haml +1 -0
  36. data/views/blurbs/edit.haml +5 -0
  37. data/views/blurbs/value.haml +9 -0
  38. data/views/index.haml +13 -0
  39. data/views/layout.haml +26 -0
  40. data/views/projects/getting_started.haml +16 -0
  41. data/views/projects/new.haml +18 -0
  42. data/views/projects/show.haml +23 -0
  43. data/views/styles.scss +218 -0
  44. data/views/users.haml +29 -0
  45. data/watchr.rb +8 -0
  46. metadata +225 -0
@@ -0,0 +1,51 @@
1
+ require 'rspec'
2
+ require 'rack/test'
3
+ require 'capybara/rspec'
4
+ require 'text_tractor'
5
+ require 'redis'
6
+
7
+ TextTractor.config do |c|
8
+ c.redis = {
9
+ ns: "text_tractor:test",
10
+ db: 8
11
+ }
12
+
13
+ c.environment = :test
14
+ c.hostname = "example.host"
15
+ c.port = 8000
16
+ c.ssl = false
17
+ end
18
+
19
+ RSpec.configure do |conf|
20
+ conf.include Rack::Test::Methods
21
+ conf.include Capybara
22
+
23
+ conf.before(:each) do
24
+ redis.flushdb
25
+ end
26
+
27
+ def redis
28
+ TextTractor.redis
29
+ end
30
+
31
+ def app
32
+ TextTractor.application
33
+ end
34
+ Capybara.app = app
35
+
36
+ def login(username = nil, password = nil)
37
+ username ||= TextTractor.configuration.default_username
38
+ password ||= TextTractor.configuration.default_password
39
+
40
+ basic_authorize username, password
41
+ Capybara.current_session.driver.basic_authorize username, password
42
+ end
43
+
44
+ def create_user(username, password, superuser = false)
45
+ TextTractor::Users.create(name: username, username: username, password: password, superuser: superuser)
46
+ end
47
+
48
+ def create_superuser(username, password)
49
+ create_user(username, password, true)
50
+ end
51
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe "authentication", :type => :request do
4
+ it "rejects the request if no authentication was provided" do
5
+ get "/"
6
+ last_response.status.should eq 401
7
+ end
8
+
9
+ it "allows access if logging in with the default credentials" do
10
+ TextTractor.configuration.default_username = "admin"
11
+ TextTractor.configuration.default_password = "password"
12
+
13
+ basic_authorize "admin", "password"
14
+
15
+ get "/"
16
+ last_response.status.should eq 200
17
+ end
18
+
19
+ describe "managing users" do
20
+ it "denies access to user who is not a superuser" do
21
+ create_user "bob@example.org", "p@ssw0rd"
22
+ login "bob@example.org", "p@ssw0rd"
23
+
24
+ visit "/"
25
+ page.should_not have_content "Users"
26
+
27
+ visit "/users"
28
+ Capybara.current_session.status_code.should eq 403
29
+ end
30
+
31
+ it "allows access to the default user" do
32
+ login
33
+
34
+ visit "/"
35
+ click_link "Users"
36
+
37
+ page.should have_content "User Management"
38
+ page.should have_content "Default User"
39
+ end
40
+
41
+ it "allows the creation of a new user" do
42
+ login
43
+
44
+ visit "/users"
45
+
46
+ page.should have_content "Create a User"
47
+
48
+ fill_in "Username", :with => "bob@example.org"
49
+ fill_in "Name", :with => "Bob Hoskins"
50
+ fill_in "Password", :with => "p@ssw0rd"
51
+ click_button "Create User"
52
+
53
+ page.should have_content "Bob Hoskins"
54
+
55
+ login "bob@example.org", "p@ssw0rd"
56
+ visit "/"
57
+ Capybara.current_session.status_code.should eq 200
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe "project management" do
4
+ context "as a superuser" do
5
+ before(:each) do
6
+ create_superuser "jim@example.org", "password"
7
+ login "jim@example.org", "password"
8
+ end
9
+
10
+ it "it lists all projects" do
11
+ TextTractor::Projects.create(name: "Test Project")
12
+ TextTractor::Projects.create(name: "User Specified Project", users: [ "jim@example.org" ])
13
+
14
+ visit '/'
15
+ page.should have_content "Test Project"
16
+ page.should have_content "User Specified Project"
17
+ end
18
+
19
+ it "it allows a new project to be created" do
20
+ visit '/'
21
+ page.should have_content "Create a Project"
22
+
23
+ click_link "Create a Project"
24
+
25
+ fill_in "Project Name", :with => "Example Project"
26
+ fill_in "Default Locale", :with => "cy" # Hell yeah, we're doing this site in Welsh.
27
+ check "jim@example.org"
28
+ click_button "Create Project"
29
+
30
+ page.should have_content "Example Project (cy)"
31
+ end
32
+
33
+ it "defaults the locale to 'en'" do
34
+ visit '/projects/new'
35
+ find_field("Default Locale").value.should == "en"
36
+ end
37
+
38
+ it "allows access to a project the user has been explicitly added to" do
39
+ project = TextTractor::Projects.create(name: "User Specified Project", users: [ "jim@example.org" ])
40
+
41
+ get "/projects/#{project["api_key"]}"
42
+ last_response.status.should eq 200
43
+ end
44
+
45
+ it "allows access to a project the user has not been added to" do
46
+ project = TextTractor::Projects.create(name: "User Specified Project")
47
+
48
+ get "/projects/#{project["api_key"]}"
49
+ last_response.status.should eq 200
50
+ end
51
+ end
52
+
53
+ context "as a normal user" do
54
+ before(:each) do
55
+ TextTractor::Projects.create(name: "Test Project", api_key: "test")
56
+ TextTractor::Projects.create(name: "User Specified Project", api_key: "user", users: [ "bob@example.org" ])
57
+
58
+ create_user "bob@example.org", "password"
59
+ login "bob@example.org", "password"
60
+ end
61
+
62
+ it "denies access to create a new project" do
63
+ visit '/'
64
+ page.should_not have_content "Create a Project"
65
+
66
+ get '/projects/new'
67
+ last_response.status.should eq 403
68
+ end
69
+
70
+ it "lists only the projects the user has been added to" do
71
+ visit '/'
72
+ page.should have_content "User Specified Project"
73
+ page.should_not have_content "Test Project"
74
+ end
75
+
76
+ it "allows access to a project the user has been added to" do
77
+ get '/projects/user'
78
+ last_response.status.should eq 200
79
+ end
80
+
81
+ it "denies access to project the user has not been added to" do
82
+ get '/projects/test'
83
+ last_response.status.should eq 403
84
+ end
85
+ end
86
+
87
+ context "when not logged in" do
88
+ it "denies access to the project list" do
89
+ get "/"
90
+ last_response.status.should eq 401
91
+ end
92
+
93
+ it "denies access to create a new project" do
94
+ post "/"
95
+ last_response.status.should eq 401
96
+ end
97
+
98
+ it "denies access to view a project" do
99
+ get "/projects/test"
100
+ last_response.status.should eq 401
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ describe "working with a project", :type => :request do
4
+ before(:each) do
5
+ @project = TextTractor::Projects.create(name: "Test Project", api_key: "test")
6
+
7
+ login
8
+ end
9
+
10
+ context "when the project has had no blurbs added to it" do
11
+ it "displays some advice on adding it to your project, including the API key" do
12
+ visit '/projects/test'
13
+
14
+ page.should have_content %q{config.api_key = "test"}
15
+ page.should have_content %q{config.host = "example.host"}
16
+ page.should have_content %q{config.port = 8000}
17
+ page.should have_content %q{config.secure = false}
18
+ end
19
+ end
20
+
21
+ context "when the project has had some blurbs added to it" do
22
+ before(:each) do
23
+ @project.update_draft_blurbs({
24
+ "en.application.home.title" => "Home Page",
25
+ "cy.application.home.title" => "Dafan",
26
+ "en.application.home.body" => "This is the home page."
27
+ })
28
+ end
29
+
30
+ it "allows the locale to be selected" do
31
+ visit '/projects/test'
32
+ within "ul#locales" do
33
+ page.should have_content "cy"
34
+ page.should have_content "en"
35
+ end
36
+
37
+ click_link "cy"
38
+ page.should have_content "Test Project (cy)"
39
+ end
40
+
41
+ it "displays a list of all the known blurbs in the default locale on the project index" do
42
+ visit '/projects/test'
43
+ page.should have_content "Application / Home / Title"
44
+ page.should have_content "Home Page"
45
+ end
46
+
47
+ it "displays a list of all the known blurbs in the selected locale if one was set" do
48
+ visit '/projects/test/cy'
49
+
50
+ page.should have_content "Application / Home / Title"
51
+ page.should have_content "Dafan"
52
+ end
53
+
54
+ it "displays the original version of each blurb if the default locale is not selected" do
55
+ visit '/projects/test/cy'
56
+ find('dd[data-key="application.home.title"] p.original').should have_content "Home Page"
57
+ end
58
+
59
+ it "displays 'Click to add a translation' if no translation has been provided" do
60
+ visit '/projects/test/cy'
61
+ find('dd[data-key="application.home.body"] p.translation').should have_content "Click to add a translation"
62
+ end
63
+
64
+ it "updates the blurb content on a POST" do
65
+ post "/projects/test/en/application/home/title", :blurb => "Test"
66
+
67
+ @project.draft_blurbs["en.application.home.title"].should == "Test"
68
+ end
69
+
70
+ it "displays the form for editing on a GET" do
71
+ visit "/projects/test/en/application/home/title"
72
+
73
+ page.find("form")["action"].should == "/projects/test/en/application/home/title"
74
+ within("textarea[name='blurb']") do
75
+ page.should have_content "Home Page"
76
+ end
77
+ end
78
+
79
+ context "when filtering to a specific state" do
80
+ before(:each) do
81
+ Time.stub(:now).and_return(Time.new(2011, 01, 01, 00, 00, 00))
82
+ @project.update_draft_blurbs({
83
+ "en.application.untranslated" => "Hello",
84
+ "en.application.translated" => "Hello",
85
+ "cy.application.translated" => "Foo",
86
+ "en.application.stale" => "Hello",
87
+ "cy.application.stale" => "Foo"
88
+ })
89
+
90
+ Time.stub(:now).and_return(Time.new(2011, 01, 01, 00, 00, 30))
91
+ @project.update_draft_blurbs({
92
+ "en.application.stale" => "Hello world"
93
+ }, overwrite: true)
94
+ end
95
+
96
+ it "shows all phrases if no filter was set" do
97
+ visit "/projects/test/cy"
98
+
99
+ page.should have_content "Application / Untranslated"
100
+ page.should have_content "Application / Translated"
101
+ page.should have_content "Application / Stale"
102
+ end
103
+
104
+ it "shows only untranslated phrases if the Untranslated filter was set" do
105
+ visit "/projects/test/cy?state=untranslated"
106
+
107
+ page.should have_content "Application / Untranslated"
108
+ page.should_not have_content "Application / Translated"
109
+ page.should_not have_content "Application / Stale"
110
+ end
111
+
112
+ it "shows only untranslated and stale phrases if the Needs Work filter was set" do
113
+ visit "/projects/test/cy?state=needs_work"
114
+
115
+ page.should have_content "Application / Untranslated"
116
+ page.should_not have_content "Application / Translated"
117
+ page.should have_content "Application / Stale"
118
+ end
119
+
120
+ it "shows only stale phrases if the Stale filter was set" do
121
+ visit "/projects/test/cy?state=stale"
122
+
123
+ page.should_not have_content "Application / Untranslated"
124
+ page.should_not have_content "Application / Translated"
125
+ page.should have_content "Application / Stale"
126
+ end
127
+
128
+ it "shows only translated phrases if the Translated filter was set" do
129
+ visit "/projects/test/cy?state=translated"
130
+
131
+ page.should_not have_content "Application / Untranslated"
132
+ page.should_not have_content "Application / Stale"
133
+ page.should have_content "Application / Translated"
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe "the UI server" do
5
+ subject { TextTractor::UiServer.new }
6
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe TextTractor::Users do
4
+ it { should_not be_nil }
5
+
6
+ specify { TextTractor::Users.should respond_to(:create) }
7
+ describe "creating a new user" do
8
+ before(:each) do
9
+ TextTractor::Users.create(username: "test", password: "example", name: "Test User", superuser: true)
10
+ end
11
+
12
+ it "adds the user's details to Redis" do
13
+ JSON.parse(TextTractor.redis.get("users:test")).should == {
14
+ "username" => "test",
15
+ "name" => "Test User",
16
+ "superuser" => true
17
+ }
18
+ end
19
+
20
+ it "adds the user's username to the list of users" do
21
+ TextTractor.redis.sismember("users", "test").should be_true
22
+ end
23
+
24
+ it "adds the user's hash for authentication" do
25
+ TextTractor.redis.sismember("user_hashes", TextTractor::Users.hash_user("test", "example")).should be_true
26
+ end
27
+
28
+ it "defaults superuser to false" do
29
+ TextTractor::Users.create(username: "foo", password: "example", name: "Test User")
30
+ JSON.parse(TextTractor.redis.get("users:foo")).should == {
31
+ "username" => "foo",
32
+ "name" => "Test User",
33
+ "superuser" => false
34
+ }
35
+ end
36
+
37
+ it "raises an Users::DuplicateUserError if the username is already in use" do
38
+ lambda { TextTractor::Users.create(username: "test", password: "example", name: "Test User") }.should raise_error(TextTractor::Users::DuplicateUserError)
39
+ end
40
+ end
41
+
42
+ specify { TextTractor::Users.should respond_to(:all) }
43
+ describe "getting the full list of users" do
44
+ before(:each) do
45
+ @user1 = TextTractor::Users.create(username: "test", password: "example", name: "Test User", superuser: false)
46
+ @user2 = TextTractor::Users.create(username: "jon", password: "test", name: "Jon Wood", superuser: true)
47
+ end
48
+
49
+ subject { TextTractor::Users.all }
50
+
51
+ it "includes all the users" do
52
+ subject.should have(2).users
53
+ end
54
+
55
+ it "doesn't include the password for a user" do
56
+ subject.first.should_not have_key("password")
57
+ end
58
+
59
+ it "includes all other details" do
60
+ subject.should == [
61
+ { "username" => "jon", "name" => "Jon Wood", "superuser" => true },
62
+ { "username" => "test", "name" => "Test User", "superuser" => false },
63
+ ]
64
+ end
65
+
66
+ it "sorts users alphabetically by their name" do
67
+ subject.first["name"].should eq "Jon Wood"
68
+ subject.last["name"].should eq "Test User"
69
+ end
70
+ end
71
+
72
+ specify { TextTractor::Users.should respond_to(:authenticate) }
73
+ describe "authenticating a user" do
74
+ before(:each) do
75
+ TextTractor::Users.create(username: "test", password: "example", name: "Test User")
76
+ end
77
+
78
+ it "returns true if the user and password match" do
79
+ TextTractor::Users.authenticate("test", "example").should be_true
80
+ end
81
+
82
+ it "returns false if only the user matches" do
83
+ TextTractor::Users.authenticate("test", "wrong").should be_false
84
+ end
85
+
86
+ it "returns false if only the password matches" do
87
+ TextTractor::Users.authenticate("wrong", "example").should be_false
88
+ end
89
+
90
+ it "returns false if neither attributes match" do
91
+ TextTractor::Users.authenticate("wrong", "wrong").should be_false
92
+ end
93
+ end
94
+
95
+ specify { TextTractor::Users.should respond_to(:get) }
96
+ describe "loading a user" do
97
+ before(:each) do
98
+ TextTractor::Users.create(username: "test", password: "example", name: "Test User")
99
+ end
100
+
101
+ it "returns the user's details if they exist" do
102
+ TextTractor::Users.get("test").should == {
103
+ "username" => "test",
104
+ "name" => "Test User",
105
+ "superuser" => false
106
+ }
107
+ end
108
+
109
+ it "returns nil if they do not exist" do
110
+ TextTractor::Users.get("bob").should be_nil
111
+ end
112
+ end
113
+
114
+ specify { TextTractor::Users.should respond_to(:exists?) }
115
+ describe "checking the existance of a user" do
116
+ before(:each) do
117
+ TextTractor::Users.create(username: "test", password: "example", name: "Test User")
118
+ end
119
+
120
+ specify { TextTractor::Users.exists?("test").should be_true }
121
+ specify { TextTractor::Users.exists?("other").should be_false }
122
+ end
123
+ end