pliny 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/pliny-generate +6 -0
- data/lib/pliny.rb +21 -0
- data/lib/pliny/commands/generator.rb +197 -0
- data/lib/pliny/config_helpers.rb +24 -0
- data/lib/pliny/errors.rb +109 -0
- data/lib/pliny/extensions/instruments.rb +38 -0
- data/lib/pliny/log.rb +84 -0
- data/lib/pliny/middleware/cors.rb +46 -0
- data/lib/pliny/middleware/request_id.rb +42 -0
- data/lib/pliny/middleware/request_store.rb +14 -0
- data/lib/pliny/middleware/rescue_errors.rb +24 -0
- data/lib/pliny/middleware/timeout.rb +25 -0
- data/lib/pliny/middleware/versioning.rb +64 -0
- data/lib/pliny/request_store.rb +22 -0
- data/lib/pliny/router.rb +15 -0
- data/lib/pliny/tasks.rb +3 -0
- data/lib/pliny/tasks/db.rake +116 -0
- data/lib/pliny/tasks/test.rake +8 -0
- data/lib/pliny/templates/endpoint.erb +30 -0
- data/lib/pliny/templates/endpoint_acceptance_test.erb +40 -0
- data/lib/pliny/templates/endpoint_scaffold.erb +49 -0
- data/lib/pliny/templates/endpoint_scaffold_acceptance_test.erb +55 -0
- data/lib/pliny/templates/endpoint_test.erb +16 -0
- data/lib/pliny/templates/mediator.erb +22 -0
- data/lib/pliny/templates/mediator_test.erb +5 -0
- data/lib/pliny/templates/migration.erb +9 -0
- data/lib/pliny/templates/model.erb +5 -0
- data/lib/pliny/templates/model_migration.erb +10 -0
- data/lib/pliny/templates/model_test.erb +5 -0
- data/lib/pliny/utils.rb +31 -0
- data/lib/pliny/version.rb +3 -0
- data/test/commands/generator_test.rb +147 -0
- data/test/errors_test.rb +24 -0
- data/test/extensions/instruments_test.rb +34 -0
- data/test/log_test.rb +27 -0
- data/test/middleware/cors_test.rb +42 -0
- data/test/middleware/request_id_test.rb +28 -0
- data/test/middleware/request_store_test.rb +25 -0
- data/test/middleware/rescue_errors_test.rb +41 -0
- data/test/middleware/timeout_test.rb +32 -0
- data/test/middleware/versioning_test.rb +63 -0
- data/test/request_store_test.rb +25 -0
- data/test/router_test.rb +39 -0
- data/test/test_helper.rb +18 -0
- metadata +252 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Endpoints
|
2
|
+
class <%= plural_class_name %> < Base
|
3
|
+
namespace "<%= url_path %>" do
|
4
|
+
before do
|
5
|
+
content_type :json
|
6
|
+
end
|
7
|
+
|
8
|
+
get do
|
9
|
+
MultiJson.encode <%= singular_class_name %>.all.map { |x| serialize(x) }
|
10
|
+
end
|
11
|
+
|
12
|
+
post do
|
13
|
+
# warning: not safe
|
14
|
+
<%= field_name %> = <%= singular_class_name %>.new(params)
|
15
|
+
<%= field_name %>.save
|
16
|
+
status 201
|
17
|
+
MultiJson.encode serialize(<%= field_name %>)
|
18
|
+
end
|
19
|
+
|
20
|
+
get "/:id" do |id|
|
21
|
+
<%= field_name %> = <%= singular_class_name %>.first(uuid: id) || halt(404)
|
22
|
+
MultiJson.encode serialize(<%= field_name %>)
|
23
|
+
end
|
24
|
+
|
25
|
+
patch "/:id" do |id|
|
26
|
+
<%= field_name %> = <%= singular_class_name %>.first(uuid: id) || halt(404)
|
27
|
+
# warning: not safe
|
28
|
+
#<%= field_name %>.update(params)
|
29
|
+
MultiJson.encode serialize(<%= field_name %>)
|
30
|
+
end
|
31
|
+
|
32
|
+
delete "/:id" do |id|
|
33
|
+
<%= field_name %> = <%= singular_class_name %>.first(uuid: id) || halt(404)
|
34
|
+
<%= field_name %>.destroy
|
35
|
+
MultiJson.encode serialize(<%= field_name %>)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def serialize(data)
|
41
|
+
{
|
42
|
+
created_at: data.created_at.try(:iso8601),
|
43
|
+
id: data.uuid,
|
44
|
+
updated_at: data.updated_at.try(:iso8601),
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Endpoints::<%= plural_class_name %> do
|
4
|
+
include Committee::Test::Methods
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def app
|
8
|
+
Routes
|
9
|
+
end
|
10
|
+
|
11
|
+
def schema_path
|
12
|
+
App.root + "/docs/schema.json"
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
@<%= field_name %> = <%= singular_class_name %>.create
|
17
|
+
|
18
|
+
# temporarily touch #updated_at until we can fix prmd
|
19
|
+
@<%= field_name %>.updated_at
|
20
|
+
@<%= field_name %>.save
|
21
|
+
end
|
22
|
+
|
23
|
+
it "GET <%= url_path %>" do
|
24
|
+
get "<%= url_path %>"
|
25
|
+
last_response.status.should eq(200)
|
26
|
+
assert_schema_conform
|
27
|
+
end
|
28
|
+
|
29
|
+
=begin
|
30
|
+
it "POST <%= url_path %>" do
|
31
|
+
post "<%= url_path %>", MultiJson.encode({})
|
32
|
+
last_response.status.should eq(201)
|
33
|
+
assert_schema_conform
|
34
|
+
end
|
35
|
+
=end
|
36
|
+
|
37
|
+
it "GET <%= url_path %>/:id" do
|
38
|
+
get "<%= url_path %>/#{@<%= field_name %>.uuid}"
|
39
|
+
last_response.status.should eq(200)
|
40
|
+
assert_schema_conform
|
41
|
+
end
|
42
|
+
|
43
|
+
it "PATCH <%= url_path %>/:id" do
|
44
|
+
header "Content-Type", "application/json"
|
45
|
+
patch "<%= url_path %>/#{@<%= field_name %>.uuid}", MultiJson.encode({})
|
46
|
+
last_response.status.should eq(200)
|
47
|
+
assert_schema_conform
|
48
|
+
end
|
49
|
+
|
50
|
+
it "GET <%= url_path %>/:id" do
|
51
|
+
delete "<%= url_path %>/#{@<%= field_name %>.uuid}"
|
52
|
+
last_response.status.should eq(200)
|
53
|
+
assert_schema_conform
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Endpoints::<%= plural_class_name %> do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
Endpoints::<%= plural_class_name %>
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "GET <%= url_path %>" do
|
11
|
+
it "succeeds" do
|
12
|
+
get "<%= url_path %>"
|
13
|
+
last_response.status.should eq(200)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%
|
2
|
+
modules = plural_class_name.split("::")
|
3
|
+
modules[0] = "Mediators::#{modules[0]}"
|
4
|
+
ident = ""
|
5
|
+
%>
|
6
|
+
<% modules[0..-2].each do |m| %>
|
7
|
+
<%= "#{ident}module #{m}\n" %>
|
8
|
+
<% ident << " " %>
|
9
|
+
<% end %>
|
10
|
+
<%= ident %>class <%= modules.last %> < Mediators::Base
|
11
|
+
<%= ident %> def initialize(args)
|
12
|
+
<%= ident %>
|
13
|
+
<%= ident %> end
|
14
|
+
|
15
|
+
<%= ident %> def call
|
16
|
+
<%= ident %>
|
17
|
+
<%= ident %> end
|
18
|
+
<%= ident %>end
|
19
|
+
<% while ident.size > 0 do %>
|
20
|
+
<% ident.chop!.chop! %>
|
21
|
+
<%= "#{ident}end\n" %>
|
22
|
+
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table(:<%= table_name %>) do
|
4
|
+
uuid :uuid, default: Sequel.function(:uuid_generate_v4), primary_key: true
|
5
|
+
timestamptz :created_at, default: Sequel.function(:now), null: false
|
6
|
+
timestamptz :updated_at
|
7
|
+
timestamptz :deleted_at
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/pliny/utils.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Pliny
|
2
|
+
module Utils
|
3
|
+
def self.parse_env(file)
|
4
|
+
env = {}
|
5
|
+
File.open(file).each do |line|
|
6
|
+
line = line.gsub(/#.*$/, '').strip
|
7
|
+
next if line.empty?
|
8
|
+
var, value = line.split("=", 2)
|
9
|
+
value.gsub!(/^['"](.*)['"]$/, '\1')
|
10
|
+
env[var] = value
|
11
|
+
end
|
12
|
+
env
|
13
|
+
end
|
14
|
+
|
15
|
+
# Requires an entire directory of source files in a stable way so that file
|
16
|
+
# hierarchy is respected for load order.
|
17
|
+
def self.require_glob(path)
|
18
|
+
files = Dir[path].sort_by do |file|
|
19
|
+
[file.count("/"), file]
|
20
|
+
end
|
21
|
+
|
22
|
+
files.each do |file|
|
23
|
+
require file
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
alias :require_relative_glob :require_glob
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Pliny::Commands::Generator do
|
4
|
+
before do
|
5
|
+
@gen = Pliny::Commands::Generator.new({}, StringIO.new)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#plural_class_name" do
|
9
|
+
it "builds a class name for a model" do
|
10
|
+
@gen.args = ["model", "resource_histories"]
|
11
|
+
assert_equal "ResourceHistories", @gen.plural_class_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#singular_class_name" do
|
16
|
+
it "builds a class name for an endpoint" do
|
17
|
+
@gen.args = ["model", "resource_histories"]
|
18
|
+
assert_equal "ResourceHistory", @gen.singular_class_name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#table_name" do
|
23
|
+
it "uses the plural form" do
|
24
|
+
@gen.args = ["model", "resource_history"]
|
25
|
+
assert_equal "resource_histories", @gen.table_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#run!" do
|
30
|
+
before do
|
31
|
+
FileUtils.mkdir_p("/tmp/plinytest")
|
32
|
+
Dir.chdir("/tmp/plinytest")
|
33
|
+
Timecop.freeze(@t=Time.now)
|
34
|
+
end
|
35
|
+
|
36
|
+
after do
|
37
|
+
FileUtils.rmdir("/tmp/plinytest")
|
38
|
+
Timecop.return
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "generating endpoints" do
|
42
|
+
before do
|
43
|
+
@gen.args = ["endpoint", "artists"]
|
44
|
+
@gen.run!
|
45
|
+
end
|
46
|
+
|
47
|
+
it "creates a new endpoint module" do
|
48
|
+
assert File.exists?("lib/endpoints/artists.rb")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "creates an endpoint test" do
|
52
|
+
assert File.exists?("spec/endpoints/artists_spec.rb")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "creates an endpoint acceptance test" do
|
56
|
+
assert File.exists?("spec/acceptance/artists_spec.rb")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "generating mediators" do
|
61
|
+
before do
|
62
|
+
@gen.args = ["mediator", "artists/creator"]
|
63
|
+
@gen.run!
|
64
|
+
end
|
65
|
+
|
66
|
+
it "creates a new endpoint module" do
|
67
|
+
assert File.exists?("lib/mediators/artists/creator.rb")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "creates a test" do
|
71
|
+
assert File.exists?("spec/mediators/artists/creator_spec.rb")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "generating models" do
|
76
|
+
before do
|
77
|
+
@gen.args = ["model", "artists"]
|
78
|
+
@gen.run!
|
79
|
+
end
|
80
|
+
|
81
|
+
it "creates a migration" do
|
82
|
+
assert File.exists?("db/migrate/#{@t.to_i}_create_artists.rb")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "creates the actual model" do
|
86
|
+
assert File.exists?("lib/models/artist.rb")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "creates a test" do
|
90
|
+
assert File.exists?("spec/models/artist_spec.rb")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "generating scaffolds" do
|
95
|
+
before do
|
96
|
+
@gen.args = ["scaffold", "artist"]
|
97
|
+
@gen.run!
|
98
|
+
end
|
99
|
+
|
100
|
+
it "creates a new endpoint module" do
|
101
|
+
assert File.exists?("lib/endpoints/artists.rb")
|
102
|
+
end
|
103
|
+
|
104
|
+
it "creates an endpoint test" do
|
105
|
+
assert File.exists?("spec/endpoints/artists_spec.rb")
|
106
|
+
end
|
107
|
+
|
108
|
+
it "creates an endpoint acceptance test" do
|
109
|
+
assert File.exists?("spec/acceptance/artists_spec.rb")
|
110
|
+
end
|
111
|
+
|
112
|
+
it "creates a migration" do
|
113
|
+
assert File.exists?("db/migrate/#{@t.to_i}_create_artists.rb")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "creates the actual model" do
|
117
|
+
assert File.exists?("lib/models/artist.rb")
|
118
|
+
end
|
119
|
+
|
120
|
+
it "creates a test" do
|
121
|
+
assert File.exists?("spec/models/artist_spec.rb")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "creates a schema" do
|
125
|
+
assert File.exists?("docs/schema/schemata/artist.yaml")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "generating schemas" do
|
130
|
+
before do
|
131
|
+
@gen.args = ["schema", "artist"]
|
132
|
+
@gen.run!
|
133
|
+
end
|
134
|
+
|
135
|
+
it "creates a schema" do
|
136
|
+
assert File.exists?("docs/schema/schemata/artist.yaml")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#url_path" do
|
142
|
+
it "builds a URL path" do
|
143
|
+
@gen.args = ["endpoint", "resource_history"]
|
144
|
+
assert_equal "/resource-histories", @gen.url_path
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/test/errors_test.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Pliny::Errors do
|
4
|
+
it "includes a general error that requires an identifier" do
|
5
|
+
e = Pliny::Errors::Error.new("General error.", :general_error)
|
6
|
+
assert_equal "General error.", e.message
|
7
|
+
assert_equal :general_error, e.id
|
8
|
+
end
|
9
|
+
|
10
|
+
it "includes an HTTP error that will take generic parameters" do
|
11
|
+
e = Pliny::Errors::HTTPStatusError.new(
|
12
|
+
"Custom HTTP error.", :custom_http_error, 499)
|
13
|
+
assert_equal "Custom HTTP error.", e.message
|
14
|
+
assert_equal :custom_http_error, e.id
|
15
|
+
assert_equal 499, e.status
|
16
|
+
end
|
17
|
+
|
18
|
+
it "includes pre-defined HTTP error templates" do
|
19
|
+
e = Pliny::Errors::NotFound.new
|
20
|
+
assert_equal "Not found.", e.message
|
21
|
+
assert_equal :not_found, e.id
|
22
|
+
assert_equal 404, e.status
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Pliny::Extensions::Instruments do
|
4
|
+
def app
|
5
|
+
Rack::Builder.new do
|
6
|
+
run Sinatra.new {
|
7
|
+
register Pliny::Extensions::Instruments
|
8
|
+
|
9
|
+
get "/apps/:id" do
|
10
|
+
status 201
|
11
|
+
"hi"
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "performs logging" do
|
18
|
+
mock(Pliny).log(hash_including(
|
19
|
+
instrumentation: true,
|
20
|
+
at: "start",
|
21
|
+
method: "GET",
|
22
|
+
path: "/apps/123",
|
23
|
+
))
|
24
|
+
mock(Pliny).log(hash_including(
|
25
|
+
instrumentation: true,
|
26
|
+
at: "finish",
|
27
|
+
method: "GET",
|
28
|
+
path: "/apps/123",
|
29
|
+
route_signature: "/apps/:id",
|
30
|
+
status: 201
|
31
|
+
))
|
32
|
+
get "/apps/123"
|
33
|
+
end
|
34
|
+
end
|
data/test/log_test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Pliny::Log do
|
4
|
+
before do
|
5
|
+
@io = StringIO.new
|
6
|
+
Pliny.stdout = @io
|
7
|
+
stub(@io).puts
|
8
|
+
end
|
9
|
+
|
10
|
+
it "logs in structured format" do
|
11
|
+
mock(@io).puts "foo=bar baz=42"
|
12
|
+
Pliny.log(foo: "bar", baz: 42)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "supports blocks to log stages and elapsed" do
|
16
|
+
mock(@io).puts "foo=bar at=start"
|
17
|
+
mock(@io).puts "foo=bar at=finish elapsed=0.000"
|
18
|
+
Pliny.log(foo: "bar") do
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "merges context from RequestStore" do
|
23
|
+
Pliny::RequestStore.store[:log_context] = { app: "pliny" }
|
24
|
+
mock(@io).puts "app=pliny foo=bar"
|
25
|
+
Pliny.log(foo: "bar")
|
26
|
+
end
|
27
|
+
end
|