TextTractor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +66 -0
- data/Rakefile +6 -0
- data/assets/images/blankpad.png +0 -0
- data/assets/images/stale.png +0 -0
- data/assets/images/translated.png +0 -0
- data/assets/images/untranslated.png +0 -0
- data/assets/js/application.js +38 -0
- data/assets/js/jquery.js +16 -0
- data/assets/js/jquery.pjax.js +188 -0
- data/config.ru +20 -0
- data/lib/text_tractor.rb +31 -0
- data/lib/text_tractor/api_server.rb +93 -0
- data/lib/text_tractor/base.rb +25 -0
- data/lib/text_tractor/config.rb +48 -0
- data/lib/text_tractor/phrase.rb +67 -0
- data/lib/text_tractor/projects.rb +202 -0
- data/lib/text_tractor/ui_server.rb +152 -0
- data/lib/text_tractor/users.rb +49 -0
- data/lib/text_tractor/users_spec.rb +10 -0
- data/lib/text_tractor/version.rb +3 -0
- data/spec/api_server_spec.rb +224 -0
- data/spec/config_spec.rb +56 -0
- data/spec/phrase_spec.rb +71 -0
- data/spec/project_spec.rb +292 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/ui_server/authentication_spec.rb +60 -0
- data/spec/ui_server/project_management_spec.rb +103 -0
- data/spec/ui_server/project_viewing_spec.rb +137 -0
- data/spec/ui_server_spec.rb +6 -0
- data/spec/users_spec.rb +123 -0
- data/text_tractor.gemspec +33 -0
- data/views/blurbs/_blurb.haml +1 -0
- data/views/blurbs/edit.haml +5 -0
- data/views/blurbs/value.haml +9 -0
- data/views/index.haml +13 -0
- data/views/layout.haml +26 -0
- data/views/projects/getting_started.haml +16 -0
- data/views/projects/new.haml +18 -0
- data/views/projects/show.haml +23 -0
- data/views/styles.scss +218 -0
- data/views/users.haml +29 -0
- data/watchr.rb +8 -0
- metadata +225 -0
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/spec/users_spec.rb
ADDED
@@ -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
|