TextTractor 0.1.0

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.
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