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
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module TextTractor
|
4
|
+
module Users
|
5
|
+
class DuplicateUserError < Exception; end
|
6
|
+
|
7
|
+
def self.redis
|
8
|
+
TextTractor.redis
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.all
|
12
|
+
redis.smembers("users").collect { |u| JSON.parse(redis.get("users:#{u}")) }.sort { |a, b| a["name"] <=> b["name"] }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.exists?(username)
|
16
|
+
redis.sismember("users", username)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.authenticate(username, password)
|
20
|
+
redis.sismember("user_hashes", hash_user(username, password))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.create(attributes = {})
|
24
|
+
attributes = TextTractor.stringify_keys(attributes)
|
25
|
+
|
26
|
+
password = attributes.delete("password")
|
27
|
+
attributes["superuser"] ||= false
|
28
|
+
|
29
|
+
if redis.setnx("users:#{attributes["username"]}", attributes.to_json)
|
30
|
+
redis.sadd("users", attributes["username"])
|
31
|
+
redis.sadd("user_hashes", hash_user(attributes["username"], password))
|
32
|
+
else
|
33
|
+
raise DuplicateUserError.new
|
34
|
+
end
|
35
|
+
|
36
|
+
attributes
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.get(username)
|
40
|
+
raw = redis.get("users:#{username}")
|
41
|
+
return JSON.parse(raw) if raw
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def self.hash_user(username, password)
|
46
|
+
Digest::MD5.hexdigest("#{username}.#{password}.#{TextTractor.configuration.salt}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TextTractor::Users do
|
4
|
+
it { should_not be_nil }
|
5
|
+
|
6
|
+
specify { TextTractor::Users.should respond_to(:all) }
|
7
|
+
specify { TextTractor::Users.should respond_to(:authenticate) }
|
8
|
+
specify { TextTractor::Users.should respond_to(:create) }
|
9
|
+
specify { TextTractor::Users.should respond_to(:get) }
|
10
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe "the API server" do
|
5
|
+
def app
|
6
|
+
TextTractor::ApiServer
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "creating and listing projects" do
|
10
|
+
it "allows the creation of a new project, returning the details" do
|
11
|
+
post "/", :name => "Test Project", :api_key => "49032804328090f8sd0fas0jds"
|
12
|
+
|
13
|
+
last_response.should be_ok
|
14
|
+
last_response.body.should == TextTractor::Project.new(
|
15
|
+
name: "Test Project",
|
16
|
+
api_key: "49032804328090f8sd0fas0jds",
|
17
|
+
default_locale: "en"
|
18
|
+
).to_json
|
19
|
+
end
|
20
|
+
|
21
|
+
it "includes a newly created project in the projects list" do
|
22
|
+
post "/", :name => "Test Project", :api_key => "bob"
|
23
|
+
get "/"
|
24
|
+
|
25
|
+
last_response.body.should == [
|
26
|
+
{ name: "Test Project", api_key: "bob", default_locale: "en", users: [] }
|
27
|
+
].to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
it "rejects a project with the same name as an existing project" do
|
31
|
+
post "/", :name => "Test Project"
|
32
|
+
post "/", :name => "Test Project"
|
33
|
+
|
34
|
+
last_response.should_not be_ok
|
35
|
+
last_response.status.should eq 422
|
36
|
+
JSON.parse(last_response.body).should == {
|
37
|
+
"error" => "The project name you specified is already in use."
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:example_phrases) do
|
43
|
+
{
|
44
|
+
"en.application.home.title" => "Home Page",
|
45
|
+
"en.application.home.body" => "This is the home page."
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "registering draft blurbs for a project" do
|
50
|
+
context "when the project exists" do
|
51
|
+
before(:each) do
|
52
|
+
post "/", :name => "Test Project", :api_key => "test"
|
53
|
+
post "/test/draft_blurbs", example_phrases.to_json
|
54
|
+
end
|
55
|
+
|
56
|
+
it "shows the update was OK" do
|
57
|
+
last_response.should be_ok
|
58
|
+
last_response.body.should == "OK"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when the project does not exist" do
|
63
|
+
it "returns a 404 error code" do
|
64
|
+
post "/test/draft_blurbs"
|
65
|
+
|
66
|
+
last_response.status.should eq 404
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "returning the draft blurbs for a project" do
|
72
|
+
context "when the project exists" do
|
73
|
+
before(:each) do
|
74
|
+
TextTractor::Projects.create name: "Test Project", api_key: "test"
|
75
|
+
|
76
|
+
# That's the format we get it in from copycopter_client
|
77
|
+
post "/test/draft_blurbs", example_phrases.to_json
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns all the translations if the ETag does not match" do
|
81
|
+
get "/test/draft_blurbs"
|
82
|
+
|
83
|
+
last_response.should be_ok
|
84
|
+
JSON.parse(last_response.body).should == example_phrases
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns a status code of 302, with an empty body, if the ETag does match" do
|
88
|
+
header "If-None-Match", redis.get("projects:test:draft_blurbs_etag")
|
89
|
+
get "/test/draft_blurbs"
|
90
|
+
|
91
|
+
last_response.status.should eq 304
|
92
|
+
last_response.body.should be_empty
|
93
|
+
end
|
94
|
+
|
95
|
+
it "does not load the translation list if the ETag matches" do
|
96
|
+
blurbs = stub(:bytesize => 0, :etag => "foo")
|
97
|
+
TextTractor::ApiServer::BlurbList.should_receive(:new).and_return(blurbs)
|
98
|
+
blurbs.should_not_receive(:each)
|
99
|
+
|
100
|
+
header "If-None-Match", "foo"
|
101
|
+
get "/test/draft_blurbs"
|
102
|
+
|
103
|
+
last_response.status.should eq 304
|
104
|
+
last_response.body.should be_empty
|
105
|
+
end
|
106
|
+
|
107
|
+
it "sets the ETag to be set" do
|
108
|
+
get "/test/draft_blurbs"
|
109
|
+
|
110
|
+
last_response.headers["ETag"].should_not be_nil
|
111
|
+
last_response.headers["ETag"].should == redis.get("projects:test:draft_blurbs_etag")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when the project does not exist" do
|
116
|
+
it "returns a 404 error code" do
|
117
|
+
get "/test/draft_blurbs"
|
118
|
+
|
119
|
+
last_response.status.should eq 404
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "returning the published blurbs for a project" do
|
125
|
+
context "when the project exists" do
|
126
|
+
before(:each) do
|
127
|
+
TextTractor::Projects.create name: "Test Project", api_key: "test"
|
128
|
+
|
129
|
+
post "/test/published_blurbs", example_phrases.to_json
|
130
|
+
end
|
131
|
+
|
132
|
+
it "returns all the translations if the ETag does not match" do
|
133
|
+
get "/test/published_blurbs"
|
134
|
+
|
135
|
+
last_response.should be_ok
|
136
|
+
JSON.parse(last_response.body).should == example_phrases
|
137
|
+
end
|
138
|
+
|
139
|
+
it "returns a status code of 302, with an empty body, if the ETag does match" do
|
140
|
+
header "If-None-Match", redis.get("projects:test:published_blurbs_etag")
|
141
|
+
get "/test/published_blurbs"
|
142
|
+
|
143
|
+
last_response.status.should eq 304
|
144
|
+
last_response.body.should be_empty
|
145
|
+
end
|
146
|
+
|
147
|
+
it "does not load the translation list if the ETag matches" do
|
148
|
+
blurbs = stub(:bytesize => 0, :etag => "foo")
|
149
|
+
TextTractor::ApiServer::BlurbList.should_receive(:new).and_return(blurbs)
|
150
|
+
blurbs.should_not_receive(:each)
|
151
|
+
|
152
|
+
header "If-None-Match", "foo"
|
153
|
+
get "/test/published_blurbs"
|
154
|
+
|
155
|
+
last_response.status.should eq 304
|
156
|
+
last_response.body.should be_empty
|
157
|
+
end
|
158
|
+
|
159
|
+
it "sets the ETag to be set" do
|
160
|
+
get "/test/published_blurbs"
|
161
|
+
|
162
|
+
last_response.headers["ETag"].should_not be_nil
|
163
|
+
last_response.headers["ETag"].should == redis.get("projects:test:published_blurbs_etag")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "when the project does not exist" do
|
168
|
+
it "returns a 404 error code" do
|
169
|
+
get "/test/draft_blurbs"
|
170
|
+
|
171
|
+
last_response.status.should eq 404
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "publishing blurbs" do
|
177
|
+
context "when the project does exist" do
|
178
|
+
before(:each) do
|
179
|
+
post "/", :name => "Test Project", :api_key => "test"
|
180
|
+
post "/test/draft_blurbs", example_phrases.to_json
|
181
|
+
end
|
182
|
+
|
183
|
+
it "has a 200 response code" do
|
184
|
+
post "/test/deploys"
|
185
|
+
last_response.status.should eq 200
|
186
|
+
end
|
187
|
+
|
188
|
+
it "marks the draft blurbs as published" do
|
189
|
+
get "/test/published_blurbs"
|
190
|
+
JSON.parse(last_response.body).should be_empty
|
191
|
+
|
192
|
+
post "/test/deploys"
|
193
|
+
get "/test/published_blurbs"
|
194
|
+
JSON.parse(last_response.body).should == example_phrases
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when the project does not exist" do
|
199
|
+
it "returns a 404 error code" do
|
200
|
+
post "/test/publish"
|
201
|
+
last_response.status.should eq 404
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe "registering multi-lingual blurbs" do
|
207
|
+
let(:example_phrases) do
|
208
|
+
{
|
209
|
+
"en.application.home.title" => "Home Page",
|
210
|
+
"cy.application.home.title" => "Hafan",
|
211
|
+
"en.application.home.body" => "This is the home page.",
|
212
|
+
"cy.application.home.body" => "Dyma hafan."
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
it "registers all the translations provided" do
|
217
|
+
post "/", :name => "Test Project", :api_key => "test"
|
218
|
+
post "/test/draft_blurbs", example_phrases.to_json
|
219
|
+
get "/test/draft_blurbs"
|
220
|
+
|
221
|
+
JSON.parse(last_response.body).should == example_phrases
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TextTractor::Config do
|
4
|
+
describe "setting the application configuration" do
|
5
|
+
specify { TextTractor.should respond_to(:config) }
|
6
|
+
|
7
|
+
it "yields an instance of TextTractor::Config" do
|
8
|
+
TextTractor.config.should do |config|
|
9
|
+
config.should be_a TextTractor::Config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "available options" do
|
15
|
+
it { should respond_to(:redis) }
|
16
|
+
it { should respond_to(:default_username) }
|
17
|
+
it { should respond_to(:default_password) }
|
18
|
+
it { should respond_to(:environment) }
|
19
|
+
it { should respond_to(:salt) }
|
20
|
+
it { should respond_to(:hostname) }
|
21
|
+
it { should respond_to(:port) }
|
22
|
+
it { should respond_to(:ssl) }
|
23
|
+
|
24
|
+
describe "setting the redis option" do
|
25
|
+
it "defaults to an empty hash" do
|
26
|
+
subject.redis.should == {}
|
27
|
+
end
|
28
|
+
|
29
|
+
it "uses the provided value if it is a Hash" do
|
30
|
+
subject.redis = { :server => "foo" }
|
31
|
+
subject.redis.should == { :server => "foo" }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "extracts the relevant details is it is a String" do
|
35
|
+
subject.redis = "redis://user:password@example.org:1234/namespace"
|
36
|
+
subject.redis.should == {
|
37
|
+
host: "example.org",
|
38
|
+
port: 1234,
|
39
|
+
username: "user",
|
40
|
+
password: "password",
|
41
|
+
ns: "namespace"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "client configuration options" do
|
48
|
+
it "defaults the port to 80" do
|
49
|
+
subject.port.should eq 80
|
50
|
+
end
|
51
|
+
|
52
|
+
it "defaults ssl to true" do
|
53
|
+
subject.ssl.should eq true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/phrase_spec.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TextTractor::Phrase do
|
4
|
+
let(:project) { TextTractor::Project.new(default_locale: "en", name: "Test Project", api_key: "test") }
|
5
|
+
subject do
|
6
|
+
TextTractor::Phrase.new(project, {
|
7
|
+
en: { "text" => "An example", "translated_at" => Time.new(2011, 01, 03, 00, 32, 00).to_s },
|
8
|
+
cy: { "text" => "An example in Welsh", "translated_at" => Time.new(2011, 01, 03, 00, 12, 00).to_s }
|
9
|
+
})
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should set the phrase's project" do
|
13
|
+
subject.project.should == project
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "converting to a hash for saving" do
|
17
|
+
specify do
|
18
|
+
subject.to_hash.should == {
|
19
|
+
"en" => { "text" => "An example", "translated_at" => Time.new(2011, 01, 03, 00, 32, 00).to_s },
|
20
|
+
"cy" => { "text" => "An example in Welsh", "translated_at" => Time.new(2011, 01, 03, 00, 12, 00).to_s }
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it { should respond_to(:[]) }
|
26
|
+
it { should respond_to(:[]=) }
|
27
|
+
|
28
|
+
describe "accessing the individual translations" do
|
29
|
+
it "allows direct access to the translated string" do
|
30
|
+
subject["en"].to_s.should == "An example"
|
31
|
+
subject["cy"].to_s.should == "An example in Welsh"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "allows the translation of a phrase to be set" do
|
35
|
+
subject["cy"] = "A new translation"
|
36
|
+
subject["cy"].to_s.should == "A new translation"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns an empty string when the translation has not been made" do
|
40
|
+
subject["de"].to_s.should == ""
|
41
|
+
subject["de"].translated_at.should be_nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "setting a translation" do
|
46
|
+
before(:each) do
|
47
|
+
Time.stub(:now).and_return(Time.new(2011, 04, 11))
|
48
|
+
end
|
49
|
+
|
50
|
+
it "sets the translation time" do
|
51
|
+
subject["de"] = "Hello!"
|
52
|
+
subject["de"].translated_at.should == Time.new(2011, 04, 11)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "translaion states" do
|
57
|
+
it "is considered translated if the default locale was translated before this translation" do
|
58
|
+
subject["cy"].translated_at = subject["en"].translated_at + 40
|
59
|
+
subject["cy"].state.should == :translated
|
60
|
+
end
|
61
|
+
|
62
|
+
it "is considered untranslated if no translation time is set" do
|
63
|
+
subject["de"].state.should == :untranslated
|
64
|
+
end
|
65
|
+
|
66
|
+
it "is considered stale if the translation time is earlier then that on the default locale" do
|
67
|
+
subject["cy"].translated_at = subject["en"].translated_at - 40
|
68
|
+
subject["cy"].state.should == :stale
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TextTractor::Project do
|
4
|
+
it { should_not be_nil }
|
5
|
+
it { should respond_to :name }
|
6
|
+
it { should respond_to :api_key }
|
7
|
+
it { should respond_to :default_locale }
|
8
|
+
it { should respond_to :users }
|
9
|
+
|
10
|
+
it "defaults the api_key to a random key" do
|
11
|
+
TextTractor::Projects.stub(:random_key).and_return("4") # Chosen by fair dice roll.
|
12
|
+
subject.api_key.should == "4"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "defaults the locale to 'en'" do
|
16
|
+
subject.default_locale.should == "en"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "defaults the users to an empty array" do
|
20
|
+
subject.users.should == []
|
21
|
+
end
|
22
|
+
|
23
|
+
specify { should respond_to(:update_draft_blurbs) }
|
24
|
+
describe "updating the draft blurbs" do
|
25
|
+
before(:each) do
|
26
|
+
Time.stub(:now).and_return(Time.new(2011, 01, 01, 00, 00, 00))
|
27
|
+
|
28
|
+
@project = TextTractor::Projects.create(name: "Test", api_key: "test")
|
29
|
+
@project.update_draft_blurbs(
|
30
|
+
"en.application.home.title" => "Home Page",
|
31
|
+
"en.application.home.body" => "This is the home page.",
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "saves the new translations" do
|
36
|
+
JSON.parse(redis.get("projects:test:draft_blurbs:application.home.title")).should == TextTractor::Phrase.new(@project, {
|
37
|
+
"en" => { "text" => "Home Page", "translated_at" => Time.now.to_s }
|
38
|
+
}).to_hash
|
39
|
+
JSON.parse(redis.get("projects:test:draft_blurbs:application.home.body")).should == TextTractor::Phrase.new(@project, {
|
40
|
+
"en" => { "text" => "This is the home page.", "translated_at" => Time.now.to_s }
|
41
|
+
}).to_hash
|
42
|
+
end
|
43
|
+
|
44
|
+
it "correctly retains quotes" do
|
45
|
+
@project.update_draft_blurbs(
|
46
|
+
"en.application.home.quoted" => %q{"I would like to test quoting." said Jon.}
|
47
|
+
)
|
48
|
+
|
49
|
+
JSON.parse(redis.get("projects:test:draft_blurbs:application.home.quoted")).should == TextTractor::Phrase.new(@project, {
|
50
|
+
"en" => { "text" => %q{"I would like to test quoting." said Jon.}, "translated_at" => Time.now.to_s }
|
51
|
+
}).to_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
it "generates a new ETag if the translations have changed" do
|
55
|
+
redis.get("projects:test:draft_blurbs_etag").should_not be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "does not replace the content of existing translations by default" do
|
59
|
+
@project.update_draft_blurbs(
|
60
|
+
"en.application.home.title" => "A different title"
|
61
|
+
)
|
62
|
+
|
63
|
+
JSON.parse(redis.get("projects:test:draft_blurbs:application.home.title")).should == TextTractor::Phrase.new(@project, {
|
64
|
+
"en" => { "text" => "Home Page", "translated_at" => Time.now.to_s }
|
65
|
+
}).to_hash
|
66
|
+
end
|
67
|
+
|
68
|
+
it "replaces the content of existing translations if :overwrite is set" do
|
69
|
+
@project.update_draft_blurbs({
|
70
|
+
"en.application.home.title" => "A different title"
|
71
|
+
}, :overwrite => true)
|
72
|
+
|
73
|
+
JSON.parse(redis.get("projects:test:draft_blurbs:application.home.title")).should == TextTractor::Phrase.new(@project, {
|
74
|
+
"en" => { "text" => "A different title", "translated_at" => Time.now.to_s }
|
75
|
+
}).to_hash
|
76
|
+
end
|
77
|
+
|
78
|
+
it "does not generate a new ETag if the translations did not change" do
|
79
|
+
previous_etag = redis.get("projects:test:draft_blurbs_etag")
|
80
|
+
|
81
|
+
@project.update_draft_blurbs(
|
82
|
+
"en.application.home.title" => "A different title"
|
83
|
+
)
|
84
|
+
redis.get("projects:test:draft_blurbs_etag").should == previous_etag
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
specify { should respond_to(:draft_blurbs) }
|
89
|
+
describe "getting the draft blurbs for a project" do
|
90
|
+
context "when the project exists" do
|
91
|
+
before(:each) do
|
92
|
+
@project = TextTractor::Projects.create(name: "Test Project", api_key: "test")
|
93
|
+
@project.update_draft_blurbs({
|
94
|
+
"en.application.home.title" => "Home Page",
|
95
|
+
"en.application.home.body" => "This is the home page."
|
96
|
+
})
|
97
|
+
end
|
98
|
+
|
99
|
+
subject { @project.draft_blurbs }
|
100
|
+
|
101
|
+
it "returns all the translations" do
|
102
|
+
subject.should == {
|
103
|
+
"en.application.home.title" => "Home Page",
|
104
|
+
"en.application.home.body" => "This is the home page."
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
specify { should respond_to(:draft_phrases) }
|
111
|
+
describe "getting the phrase list for a project" do
|
112
|
+
before(:each) do
|
113
|
+
Time.stub(:now).and_return(Time.new(2011, 01, 01, 00, 00, 00))
|
114
|
+
@project = TextTractor::Projects.create(name: "Test Project", api_key: "test")
|
115
|
+
@project.update_draft_blurbs({
|
116
|
+
"en.application.home.title" => "Home Page",
|
117
|
+
"cy.application.home.title" => "Dafan",
|
118
|
+
"en.application.home.body" => "This is the home page."
|
119
|
+
})
|
120
|
+
end
|
121
|
+
|
122
|
+
it "returns the list of phrases, in all known languages" do
|
123
|
+
@project.draft_phrases.inject({}) { |memo, value| memo[value[0]] = value[1].to_hash; memo }.should == {
|
124
|
+
"application.home.title" => TextTractor::Phrase.new(@project, {
|
125
|
+
"en" => { "text" => "Home Page", "translated_at" => Time.now.to_s },
|
126
|
+
"cy" => { "text" => "Dafan", "translated_at" => Time.now.to_s }
|
127
|
+
}).to_hash,
|
128
|
+
"application.home.body" => TextTractor::Phrase.new(@project, {
|
129
|
+
"en" => { "text" => "This is the home page.", "translated_at" => Time.now.to_s }
|
130
|
+
}).to_hash
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
specify { should respond_to(:locales) }
|
136
|
+
describe "getting the list of known locales" do
|
137
|
+
before(:each) do
|
138
|
+
@project = TextTractor::Projects.create(name: "Test Project", api_key: "test")
|
139
|
+
@project.update_draft_blurbs({
|
140
|
+
"en.application.home.title" => "Home Page",
|
141
|
+
"cy.application.home.title" => "Dafan",
|
142
|
+
"en.application.home.body" => "This is the home page."
|
143
|
+
})
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns the known locales" do
|
147
|
+
@project.locales.should == [ "cy", "en" ]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe TextTractor::Projects do
|
153
|
+
specify { TextTractor::Projects.should respond_to(:create) }
|
154
|
+
|
155
|
+
describe "creating a new project" do
|
156
|
+
context "when succesful" do
|
157
|
+
before(:each) { @project = TextTractor::Projects.create name: "Test Project", api_key: "49032804328090f8sd0fas0jds", users: [ "jon@blankpad.net", "bob@example.org" ] }
|
158
|
+
subject { @project }
|
159
|
+
|
160
|
+
it "returns the details as a project instance" do
|
161
|
+
subject.should be_instance_of TextTractor::Project
|
162
|
+
|
163
|
+
subject.name.should == "Test Project"
|
164
|
+
subject.api_key.should == "49032804328090f8sd0fas0jds"
|
165
|
+
end
|
166
|
+
|
167
|
+
it "saves the projects details for later use" do
|
168
|
+
TextTractor.redis.get("projects:49032804328090f8sd0fas0jds").should == subject.to_json
|
169
|
+
end
|
170
|
+
|
171
|
+
it "adds the API key to the project index" do
|
172
|
+
TextTractor.redis.sismember("projects", "49032804328090f8sd0fas0jds").should be_true
|
173
|
+
end
|
174
|
+
|
175
|
+
it "places the project name in a set for quick reference" do
|
176
|
+
TextTractor.redis.sismember("project_names", "Test Project").should be_true
|
177
|
+
end
|
178
|
+
|
179
|
+
it "assigns any provided users to the project" do
|
180
|
+
TextTractor.redis.sismember("project_users:49032804328090f8sd0fas0jds", "bob@example.org").should be_true
|
181
|
+
TextTractor.redis.sismember("project_users:49032804328090f8sd0fas0jds", "jon@blankpad.net").should be_true
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it "rejects a project with the same name as an existing project" do
|
186
|
+
TextTractor::Projects.create name: "Test Project"
|
187
|
+
|
188
|
+
lambda { TextTractor::Projects.create name: "Test Project" }.should raise_error(TextTractor::Projects::DuplicateProjectName)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "getting an existing project" do
|
193
|
+
it "returns the project on success" do
|
194
|
+
TextTractor::Projects.create name: "Test Project", api_key: "test"
|
195
|
+
|
196
|
+
project = TextTractor::Projects.get("test")
|
197
|
+
project.should be_instance_of TextTractor::Project
|
198
|
+
project.name.should == "Test Project"
|
199
|
+
project.api_key.should == "test"
|
200
|
+
end
|
201
|
+
|
202
|
+
it "returns nil if the project did not exist" do
|
203
|
+
TextTractor::Projects.get("test").should be_nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "listing projects for a user" do
|
208
|
+
before(:each) do
|
209
|
+
TextTractor::Projects.create(name: "Assigned Project", users: [ "example" ])
|
210
|
+
TextTractor::Projects.create(name: "Unassigned Project")
|
211
|
+
end
|
212
|
+
|
213
|
+
let(:user) do
|
214
|
+
{ "username" => "example",
|
215
|
+
"superuser" => false }
|
216
|
+
end
|
217
|
+
|
218
|
+
specify { TextTractor::Projects.should respond_to(:for_user) }
|
219
|
+
|
220
|
+
it "returns all projects if the user is a superuser" do
|
221
|
+
user["superuser"] = true
|
222
|
+
projects = TextTractor::Projects.for_user(user)
|
223
|
+
|
224
|
+
projects.should have(2).projects
|
225
|
+
projects.first["name"].should eq "Assigned Project"
|
226
|
+
projects.last["name"].should eq "Unassigned Project"
|
227
|
+
end
|
228
|
+
|
229
|
+
it "returns only projects the user has been added to for standard users" do
|
230
|
+
user["superuser"] = false
|
231
|
+
projects = TextTractor::Projects.for_user(user)
|
232
|
+
|
233
|
+
projects.should have(1).project
|
234
|
+
projects.first["name"].should eq "Assigned Project"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
specify { TextTractor::Projects.should respond_to(:authorised?) }
|
239
|
+
describe "checking authorisation for a project" do
|
240
|
+
before(:each) do
|
241
|
+
TextTractor::Projects.create(name: "Test", api_key: "test", users: [ "bob@example.org" ])
|
242
|
+
end
|
243
|
+
|
244
|
+
it "returns true if the user is a super user" do
|
245
|
+
TextTractor::Projects.authorised?({ "superuser" => true }, "test")
|
246
|
+
end
|
247
|
+
|
248
|
+
it "returns true if the user is in the list of assigned users for the project" do
|
249
|
+
TextTractor::Projects.authorised?({ "superuser" => false, "username" => "bob@example.org" }, "test")
|
250
|
+
end
|
251
|
+
|
252
|
+
it "returns false if the user is not in the list of assigned users for the project" do
|
253
|
+
TextTractor::Projects.authorised?({ "superuser" => false, "username" => "frank@example.org" }, "test")
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
specify { TextTractor::Projects.should respond_to(:update_datastore) }
|
258
|
+
describe "migrating a data store to the current version" do
|
259
|
+
before(:each) do
|
260
|
+
Time.stub(:now).and_return(Time.new(2011, 01, 01, 00, 00, 00))
|
261
|
+
TextTractor::Projects.create(name: "Test", api_key: "test")
|
262
|
+
|
263
|
+
redis.set("projects:test:draft_blurbs:en.application.home.title", "Home page")
|
264
|
+
redis.sadd("projects:test:draft_blurb_keys", "en.application.home.title")
|
265
|
+
redis.set("projects:test:draft_blurbs:cy.application.home.title", "Hafan")
|
266
|
+
redis.sadd("projects:test:draft_blurb_keys", "cy.application.home.title")
|
267
|
+
redis.set "projects:test:draft_blurbs_etag", "old_etag"
|
268
|
+
|
269
|
+
TextTractor::Projects.update_datastore
|
270
|
+
end
|
271
|
+
|
272
|
+
it "updates the ETag" do
|
273
|
+
redis.get("projects:test:draft_blurbs_etag").should_not == "old_etag"
|
274
|
+
end
|
275
|
+
|
276
|
+
it "places all translations of a phrase under one key" do
|
277
|
+
redis.sismember("projects:test:draft_blurb_keys", "application.home.title").should be_true
|
278
|
+
redis.get("projects:test:draft_blurbs:application.home.title").should == {
|
279
|
+
"en" => { "text" => "Home page", "translated_at" => Time.now.to_s },
|
280
|
+
"cy" => { "text" => "Hafan", "translated_at" => Time.now.to_s }
|
281
|
+
}.to_json
|
282
|
+
end
|
283
|
+
|
284
|
+
it "removes the old blurbs" do
|
285
|
+
redis.sismember("projects:test:draft_blurb_keys", "en.application.home.title").should_not be_true
|
286
|
+
redis.sismember("projects:test:draft_blurb_keys", "cy.application.home.title").should_not be_true
|
287
|
+
|
288
|
+
redis.get("projects:test:draft_blurbs:en.application.home.title").should be_nil
|
289
|
+
redis.get("projects:test:draft_blurbs:cy.application.home.title").should be_nil
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|