pliny 0.0.1.pre
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.
- 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
|