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