keight 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGES.txt +64 -0
- data/MIT-LICENSE +1 -1
- data/README.md +180 -55
- data/Rakefile +1 -1
- data/bench/bench.rb +219 -119
- data/bench/benchmarker.rb +5 -3
- data/bin/k8rb +237 -20
- data/keight.gemspec +3 -3
- data/lib/keight.rb +373 -634
- data/lib/keight/skeleton/README.txt +13 -0
- data/lib/keight/skeleton/config.ru +3 -18
- data/lib/keight/skeleton/index.txt +7 -6
- data/lib/keight/skeleton/main.rb +22 -0
- data/lib/keight/skeleton/static/lib/.keep +0 -0
- data/lib/keight/skeleton/test/api/hello_test.rb +27 -0
- data/lib/keight/skeleton/test/test_helper.rb +9 -0
- data/test/keight_test.rb +783 -938
- data/test/oktest.rb +3 -3
- metadata +29 -7
- data/lib/keight/skeleton/static/lib/jquery/1.11.3/jquery.min.js +0 -6
- data/lib/keight/skeleton/static/lib/jquery/1.11.3/jquery.min.js.gz +0 -0
- data/lib/keight/skeleton/static/lib/modernizr/2.8.3/modernizr.min.js +0 -4
- data/lib/keight/skeleton/static/lib/modernizr/2.8.3/modernizr.min.js.gz +0 -0
@@ -1,21 +1,6 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
-
|
3
|
+
require_relative './main'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
## get $urlpath_mapping
|
9
|
-
require_relative 'config/urlpath_mapping'
|
10
|
-
|
11
|
-
## create application object
|
12
|
-
opts = $config.get_all(:k8_rackapp_) # ex: {urlpath_cache_size: 1000}
|
13
|
-
app = K8::RackApplication.new($urlpath_mapping, opts)
|
14
|
-
$urlpath_mapping = nil
|
15
|
-
|
16
|
-
## cookie store session
|
17
|
-
require 'rack/session/cookie'
|
18
|
-
use Rack::Session::Cookie, $config.get_all(:session_)
|
19
|
-
|
20
|
-
## start application
|
21
|
-
run app
|
5
|
+
# start application
|
6
|
+
run $main_app
|
@@ -27,12 +27,13 @@ app/template/ template files
|
|
27
27
|
app/template/_layout.html.eruby default layout template
|
28
28
|
app/template/welcome.html.eruby welcome page template
|
29
29
|
static/ static files
|
30
|
-
static/lib/
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
static/lib/ libraries such as jquery
|
31
|
+
test/ test scripts
|
32
|
+
test/api/
|
33
|
+
test/api/hello_test.rb
|
34
|
+
test/test_helper.rb
|
35
35
|
tmp/ temporary directory
|
36
36
|
tmp/upload/ for uploaded files
|
37
37
|
.gitignore for Git
|
38
|
-
|
38
|
+
Gemfile gems list
|
39
|
+
main.rb create Rack app
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'keight'
|
4
|
+
|
5
|
+
## create $config object
|
6
|
+
require_relative 'config'
|
7
|
+
|
8
|
+
## get $urlpath_mapping
|
9
|
+
require_relative 'config/urlpath_mapping'
|
10
|
+
|
11
|
+
## create application object
|
12
|
+
opts = $config.get_all(:k8_rackapp_) # ex: {urlpath_cache_size: 1000}
|
13
|
+
app = K8::RackApplication.new($urlpath_mapping, opts)
|
14
|
+
$urlpath_mapping = nil
|
15
|
+
$k8_app = app
|
16
|
+
|
17
|
+
## cookie store session
|
18
|
+
require 'rack/session/cookie'
|
19
|
+
app = Rack::Session::Cookie.new(app, $config.get_all(:session_))
|
20
|
+
|
21
|
+
## export as $main_app
|
22
|
+
$main_app = app
|
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative '../test_helper'
|
4
|
+
|
5
|
+
|
6
|
+
http = Rack::TestApp.wrap($main_app)
|
7
|
+
|
8
|
+
|
9
|
+
describe HelloAPI do
|
10
|
+
|
11
|
+
|
12
|
+
describe 'GET /api/hello' do
|
13
|
+
|
14
|
+
it "returns JSON data." do
|
15
|
+
r = http.GET('/api/hello')
|
16
|
+
result = http.GET('/api/hello')
|
17
|
+
r = result
|
18
|
+
ok {r.status} == 200
|
19
|
+
ok {r.content_type} == "application/json"
|
20
|
+
#ok {r.body_json} == {"message"=>"Hello"}
|
21
|
+
ok {r.body_json} == {"action"=>"index", "query"=>{}}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
data/test/keight_test.rb
CHANGED
@@ -5,6 +5,7 @@ $LOAD_PATH << "test" unless $LOAD_PATH.include?("test")
|
|
5
5
|
|
6
6
|
require 'stringio'
|
7
7
|
|
8
|
+
require 'rack/test_app'
|
8
9
|
require 'oktest'
|
9
10
|
|
10
11
|
require 'keight'
|
@@ -92,7 +93,7 @@ end
|
|
92
93
|
Oktest.scope do
|
93
94
|
|
94
95
|
def new_env(meth="GET", path="/", opts={})
|
95
|
-
return
|
96
|
+
return Rack::TestApp.new_env(meth, path, opts)
|
96
97
|
end
|
97
98
|
|
98
99
|
|
@@ -374,6 +375,26 @@ Oktest.scope do
|
|
374
375
|
end
|
375
376
|
|
376
377
|
|
378
|
+
topic '#method()' do
|
379
|
+
|
380
|
+
fixture :req do
|
381
|
+
K8::Request.new({"REQUEST_METHOD"=>"POST"})
|
382
|
+
end
|
383
|
+
|
384
|
+
spec "[!084jo] returns current request method when argument is not specified." do
|
385
|
+
|req|
|
386
|
+
ok {req.method} == :POST
|
387
|
+
end
|
388
|
+
|
389
|
+
spec "[!gwskf] calls Object#method() when argument specified." do
|
390
|
+
|req|
|
391
|
+
ok {req.method(:method)} != :POST
|
392
|
+
ok {req.method(:method)}.is_a?(Method)
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|
396
|
+
|
397
|
+
|
377
398
|
topic '#header()' do
|
378
399
|
|
379
400
|
spec "[!1z7wj] returns http header value from environment." do
|
@@ -388,24 +409,6 @@ Oktest.scope do
|
|
388
409
|
end
|
389
410
|
|
390
411
|
|
391
|
-
topic '#method()' do
|
392
|
-
|
393
|
-
spec "[!tp595] returns :GET, :POST, :PUT, ... when argument is not passed." do
|
394
|
-
ok {K8::Request.new(new_env('GET', '/')).method} == :GET
|
395
|
-
ok {K8::Request.new(new_env('POST', '/')).method} == :POST
|
396
|
-
ok {K8::Request.new(new_env('PUT', '/')).method} == :PUT
|
397
|
-
ok {K8::Request.new(new_env('DELETE', '/')).method} == :DELETE
|
398
|
-
end
|
399
|
-
|
400
|
-
spec "[!49f51] returns Method object when argument is passed." do
|
401
|
-
req = K8::Request.new(new_env('GET', '/'))
|
402
|
-
ok {req.method('env')}.is_a?(Method)
|
403
|
-
ok {req.method('env').call()}.same?(req.env)
|
404
|
-
end
|
405
|
-
|
406
|
-
end
|
407
|
-
|
408
|
-
|
409
412
|
topic '#request_method' do
|
410
413
|
|
411
414
|
spec "[!y8eos] returns env['REQUEST_METHOD'] as string." do
|
@@ -636,7 +639,7 @@ Oktest.scope do
|
|
636
639
|
topic '#cookies' do
|
637
640
|
|
638
641
|
spec "[!c9pwr] parses cookie data and returns it as hash object." do
|
639
|
-
req = K8::Request.new(new_env('POST', '/',
|
642
|
+
req = K8::Request.new(new_env('POST', '/', cookies: "aaa=homhom; bbb=madmad"))
|
640
643
|
ok {req.cookies} == {"aaa"=>"homhom", "bbb"=>"madmad"}
|
641
644
|
end
|
642
645
|
|
@@ -785,6 +788,49 @@ Oktest.scope do
|
|
785
788
|
end
|
786
789
|
|
787
790
|
|
791
|
+
topic '._build_action_info()' do
|
792
|
+
|
793
|
+
spec "[!ordhc] build ActionInfo objects for each action methods." do
|
794
|
+
infos = BooksAction._build_action_info('/api/books')
|
795
|
+
#
|
796
|
+
ok {infos[:do_index]}.is_a?(K8::ActionInfo)
|
797
|
+
ok {infos[:do_index].method} == :GET
|
798
|
+
ok {infos[:do_index].urlpath} == '/api/books/'
|
799
|
+
#
|
800
|
+
ok {infos[:do_update]}.is_a?(K8::ActionInfo)
|
801
|
+
ok {infos[:do_update].method} == :PUT
|
802
|
+
ok {infos[:do_update].urlpath(123)} == '/api/books/123'
|
803
|
+
end
|
804
|
+
|
805
|
+
end
|
806
|
+
|
807
|
+
|
808
|
+
topic '.[]' do
|
809
|
+
|
810
|
+
spec "[!1tq8z] returns ActionInfo object corresponding to action method." do
|
811
|
+
BooksAction._build_action_info('/api/books')
|
812
|
+
cls = BooksAction
|
813
|
+
#
|
814
|
+
ok {cls[:do_create]}.is_a?(K8::ActionInfo)
|
815
|
+
ok {cls[:do_create].method} == :POST
|
816
|
+
ok {cls[:do_create].urlpath} == '/api/books/'
|
817
|
+
#
|
818
|
+
ok {cls[:do_show]}.is_a?(K8::ActionInfo)
|
819
|
+
ok {cls[:do_show].method} == :GET
|
820
|
+
ok {cls[:do_show].urlpath(123)} == '/api/books/123'
|
821
|
+
end
|
822
|
+
|
823
|
+
spec "[!6g2iw] returns nil when not mounted yet." do
|
824
|
+
class ExampleClass2 < K8::BaseAction
|
825
|
+
mapping '', :GET=>:do_index
|
826
|
+
def do_index; end
|
827
|
+
end
|
828
|
+
ok {ExampleClass2[:do_index]} == nil
|
829
|
+
end
|
830
|
+
|
831
|
+
end
|
832
|
+
|
833
|
+
|
788
834
|
end
|
789
835
|
|
790
836
|
|
@@ -1397,6 +1443,83 @@ Oktest.scope do
|
|
1397
1443
|
end
|
1398
1444
|
|
1399
1445
|
|
1446
|
+
topic K8::ActionInfo do
|
1447
|
+
|
1448
|
+
|
1449
|
+
topic '.create()' do
|
1450
|
+
|
1451
|
+
spec "[!1nk0i] replaces urlpath parameters with '%s'." do
|
1452
|
+
info = K8::ActionInfo.create('GET', '/books/{id}/comments/{comment_id}')
|
1453
|
+
actual = info.instance_variable_get('@urlpath_format')
|
1454
|
+
ok {actual} == '/books/%s/comments/%s'
|
1455
|
+
#
|
1456
|
+
info = K8::ActionInfo.create('GET', '/books')
|
1457
|
+
actual = info.instance_variable_get('@urlpath_format')
|
1458
|
+
ok {actual} == '/books'
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
spec "[!a7fqv] replaces '%' with'%%'." do
|
1462
|
+
info = K8::ActionInfo.create('GET', '/books%9A%9B/{id}')
|
1463
|
+
actual = info.instance_variable_get('@urlpath_format')
|
1464
|
+
ok {actual} == '/books%%9A%%9B/%s'
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
spec "[!btt2g] returns ActionInfoN object when number of urlpath parameter <= 4." do
|
1468
|
+
info = K8::ActionInfo.create('GET', '/books')
|
1469
|
+
ok {info}.is_a?(K8::ActionInfo0)
|
1470
|
+
ok {info.urlpath} == '/books'
|
1471
|
+
ok {->{ info.urlpath('a') }}.raise?(ArgumentError, /^wrong number of arguments \((1 for 0|given 1, expected 0)\)$/)
|
1472
|
+
#
|
1473
|
+
info = K8::ActionInfo.create('GET', '/books/{id}')
|
1474
|
+
ok {info}.is_a?(K8::ActionInfo1)
|
1475
|
+
ok {info.urlpath('a')} == '/books/a'
|
1476
|
+
ok {->{ info.urlpath() }}.raise?(ArgumentError, /^wrong number of arguments \((0 for 1|given 0, expected 1)\)$/)
|
1477
|
+
#
|
1478
|
+
info = K8::ActionInfo.create('GET', '/books/{id}/comments/{comment_id}')
|
1479
|
+
ok {info}.is_a?(K8::ActionInfo2)
|
1480
|
+
ok {info.urlpath('a', 'b')} == '/books/a/comments/b'
|
1481
|
+
ok {->{info.urlpath('a')}}.raise?(ArgumentError, /^wrong number of arguments \((1 for 2|given 1, expected 2)\)$/)
|
1482
|
+
#
|
1483
|
+
info = K8::ActionInfo.create('GET', '/books/{id}/{title}/{code}')
|
1484
|
+
ok {info}.is_a?(K8::ActionInfo3)
|
1485
|
+
ok {info.urlpath('a', 'b', 'c')} == '/books/a/b/c'
|
1486
|
+
ok {->{info.urlpath(1,2)}}.raise?(ArgumentError, /^wrong number of arguments \((2 for 3|given 2, expected 3)\)$/)
|
1487
|
+
#
|
1488
|
+
info = K8::ActionInfo.create('GET', '/books/{id}/{title}/{code}/{ref}')
|
1489
|
+
ok {info}.is_a?(K8::ActionInfo4)
|
1490
|
+
ok {info.urlpath('a', 'b', 'c', 'd')} == '/books/a/b/c/d'
|
1491
|
+
ok {->{info.urlpath}}.raise?(ArgumentError, /^wrong number of arguments \((0 for 4|given 0, expected 4)\)$/)
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
spec "[!x5yx2] returns ActionInfo object when number of urlpath parameter > 4." do
|
1495
|
+
info = K8::ActionInfo.create('GET', '/books/{id}/{title}/{code}/{ref}/{x}')
|
1496
|
+
ok {info}.is_a?(K8::ActionInfo)
|
1497
|
+
ok {info.urlpath('a', 'b', 'c', 'd', 'e')} == "/books/a/b/c/d/e"
|
1498
|
+
#
|
1499
|
+
ok {->{info.urlpath('a','b','c')}}.raise?(ArgumentError, "too few arguments")
|
1500
|
+
end
|
1501
|
+
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
|
1505
|
+
topic '#form_action_attr()' do
|
1506
|
+
|
1507
|
+
spec "[!qyhkm] returns '/api/books/123' when method is POST." do
|
1508
|
+
info = K8::ActionInfo.create('POST', '/api/books/{id}')
|
1509
|
+
ok {info.form_action_attr(123)} == '/api/books/123'
|
1510
|
+
end
|
1511
|
+
|
1512
|
+
spec "[!kogyx] returns '/api/books/123?_method=PUT' when method is not POST." do
|
1513
|
+
info = K8::ActionInfo.create('PUT', '/api/books/{id}')
|
1514
|
+
ok {info.form_action_attr(123)} == '/api/books/123?_method=PUT'
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
end
|
1518
|
+
|
1519
|
+
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
|
1400
1523
|
topic K8::DefaultPatterns do
|
1401
1524
|
|
1402
1525
|
|
@@ -1463,6 +1586,42 @@ Oktest.scope do
|
|
1463
1586
|
end
|
1464
1587
|
|
1465
1588
|
|
1589
|
+
topic K8::DEFAULT_PATTERNS do
|
1590
|
+
|
1591
|
+
spec "[!i51id] registers '\d+' as default pattern of param 'id' or /_id\z/." do
|
1592
|
+
pat, proc_ = K8::DEFAULT_PATTERNS.lookup('id')
|
1593
|
+
ok {pat} == '\d+'
|
1594
|
+
ok {proc_.call("123")} == 123
|
1595
|
+
pat, proc_ = K8::DEFAULT_PATTERNS.lookup('book_id')
|
1596
|
+
ok {pat} == '\d+'
|
1597
|
+
ok {proc_.call("123")} == 123
|
1598
|
+
end
|
1599
|
+
|
1600
|
+
spec "[!2g08b] registers '(?:\.\w+)?' as default pattern of param 'ext'." do
|
1601
|
+
pat, proc_ = K8::DEFAULT_PATTERNS.lookup('ext')
|
1602
|
+
ok {pat} == '(?:\.\w+)?'
|
1603
|
+
ok {proc_} == nil
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
spec "[!8x5mp] registers '\d\d\d\d-\d\d-\d\d' as default pattern of param 'date' or /_date\z/." do
|
1607
|
+
pat, proc_ = K8::DEFAULT_PATTERNS.lookup('date')
|
1608
|
+
ok {pat} == '\d\d\d\d-\d\d-\d\d'
|
1609
|
+
ok {proc_.call("2014-12-24")} == Date.new(2014, 12, 24)
|
1610
|
+
pat, proc_ = K8::DEFAULT_PATTERNS.lookup('birth_date')
|
1611
|
+
ok {pat} == '\d\d\d\d-\d\d-\d\d'
|
1612
|
+
ok {proc_.call("2015-02-14")} == Date.new(2015, 2, 14)
|
1613
|
+
end
|
1614
|
+
|
1615
|
+
spec "[!wg9vl] raises 404 error when invalid date (such as 2012-02-30)." do
|
1616
|
+
pat, proc_ = K8::DEFAULT_PATTERNS.lookup('date')
|
1617
|
+
pr = proc { proc_.call('2012-02-30') }
|
1618
|
+
ok {pr}.raise?(K8::HttpException, "2012-02-30: invalid date.")
|
1619
|
+
ok {pr.exception.status_code} == 404
|
1620
|
+
end
|
1621
|
+
|
1622
|
+
end
|
1623
|
+
|
1624
|
+
|
1466
1625
|
topic K8::ActionMethodMapping do
|
1467
1626
|
|
1468
1627
|
fixture :mapping do
|
@@ -1520,673 +1679,681 @@ Oktest.scope do
|
|
1520
1679
|
end
|
1521
1680
|
|
1522
1681
|
|
1523
|
-
topic K8::
|
1682
|
+
topic K8::ActionMapping do
|
1524
1683
|
|
1525
|
-
fixture :mapping do
|
1526
|
-
K8::ActionClassMapping.new
|
1527
|
-
end
|
1528
|
-
|
1529
|
-
fixture :proc_obj1 do
|
1530
|
-
_, proc_obj = K8::DEFAULT_PATTERNS.lookup('id')
|
1531
|
-
proc_obj
|
1532
|
-
end
|
1533
1684
|
|
1534
|
-
|
1535
|
-
_, proc_obj = K8::DEFAULT_PATTERNS.lookup('book_id')
|
1536
|
-
proc_obj
|
1537
|
-
end
|
1538
|
-
|
1539
|
-
topic '#mount()' do
|
1685
|
+
topic '#initialize()' do
|
1540
1686
|
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1687
|
+
spec "[!buj0d] prepares LRU cache if cache size specified." do
|
1688
|
+
mapping = K8::ActionMapping.new([], urlpath_cache_size: 3)
|
1689
|
+
mapping.instance_exec(self) do |_|
|
1690
|
+
_.ok {@urlpath_cache_size} == 3
|
1691
|
+
_.ok {@urlpath_lru_cache} == {}
|
1546
1692
|
end
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
mapping '', :GET=>:do_index
|
1553
|
-
def do_index; ''; end
|
1554
|
-
class MyError < Exception
|
1555
|
-
end
|
1556
|
-
end
|
1557
|
-
#
|
1558
|
-
module Admin
|
1559
|
-
class Admin::BooksAPI < K8::Action
|
1560
|
-
mapping '', :GET=>:do_index
|
1561
|
-
def do_index; ''; end
|
1562
|
-
end
|
1563
|
-
end
|
1564
|
-
END
|
1693
|
+
#
|
1694
|
+
mapping = K8::ActionMapping.new([], urlpath_cache_size: 0)
|
1695
|
+
mapping.instance_exec(self) do |_|
|
1696
|
+
_.ok {@urlpath_cache_size} == 0
|
1697
|
+
_.ok {@urlpath_lru_cache} == nil
|
1565
1698
|
end
|
1566
|
-
'./testapi/books:BooksAction'
|
1567
1699
|
end
|
1568
1700
|
|
1569
|
-
spec "[!
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
ok {arr[0][1]} == BooksAction
|
1701
|
+
spec "[!wsz8g] compiles urlpath mapping passed." do
|
1702
|
+
mapping = K8::ActionMapping.new([
|
1703
|
+
['/api/books', BooksAction],
|
1704
|
+
])
|
1705
|
+
mapping.instance_exec(self) do |_|
|
1706
|
+
_.ok {@urlpath_rexp} == %r'\A/api/books(?:/\d+(\z)|/\d+/edit(\z))\z'
|
1707
|
+
_.ok {@fixed_endpoints.keys} == ['/api/books/', '/api/books/new']
|
1708
|
+
_.ok {@variable_endpoints.map{|x| x[0]}} == ['/api/books/{id}', '/api/books/{id}/edit']
|
1709
|
+
end
|
1579
1710
|
end
|
1580
1711
|
|
1581
|
-
spec "[!
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1712
|
+
spec "[!34o67] keyword arg 'enable_urlpath_param_range' controls to generate range object or not." do
|
1713
|
+
arr = [['/books', BooksAction]]
|
1714
|
+
#
|
1715
|
+
mapping1 = K8::ActionMapping.new(arr, enable_urlpath_param_range: true)
|
1716
|
+
mapping1.instance_exec(self) do |_|
|
1717
|
+
tuple = @variable_endpoints.find {|a| a[0] == '/books/{id}' }
|
1718
|
+
_.ok {tuple[-1]} == (7..-1)
|
1719
|
+
end
|
1720
|
+
#
|
1721
|
+
mapping2 = K8::ActionMapping.new(arr, enable_urlpath_param_range: false)
|
1722
|
+
mapping2.instance_exec(self) do |_|
|
1723
|
+
tuple = @variable_endpoints.find {|a| a[0] == '/books/{id}' }
|
1724
|
+
_.ok {tuple[-1]} == nil
|
1725
|
+
end
|
1592
1726
|
end
|
1593
1727
|
|
1594
|
-
|
1728
|
+
end
|
1595
1729
|
|
1596
|
-
spec "[!9brqr] raises error when string format is invalid." do
|
1597
|
-
|mapping, testapi_books|
|
1598
|
-
pr = proc { mapping.mount '/books', 'books.MyBooksAPI' }
|
1599
|
-
ok {pr}.raise?(ArgumentError, "mount('books.MyBooksAPI'): expected 'file/path:ClassName'.")
|
1600
|
-
end
|
1601
1730
|
|
1602
|
-
|
1603
|
-
|mapping, testapi_books|
|
1604
|
-
pr = proc { mapping.mount '/books', './testapi/books:MyBooksAPI' }
|
1605
|
-
ok {pr}.NOT.raise?(Exception)
|
1606
|
-
ok {MyBooksAPI}.is_a?(Class)
|
1607
|
-
end
|
1731
|
+
topic '#compile()' do
|
1608
1732
|
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
ok {pr}.raise?(ArgumentError, "mount('./testapi/books999:MyBooksAPI'): failed to require file.")
|
1613
|
-
end
|
1733
|
+
fixture :proc1 do
|
1734
|
+
proc {|x| x.to_i }
|
1735
|
+
end
|
1614
1736
|
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
ok {filepath}.NOT.exist?
|
1619
|
-
File.open(filepath, 'w') {|f| f << "require 'homhom7'\n" }
|
1620
|
-
pr = proc { mapping.mount '/books', './testapi/books7:MyBooks7API' }
|
1621
|
-
ok {pr}.raise?(LoadError, "cannot load such file -- homhom7")
|
1622
|
-
end
|
1737
|
+
fixture :proc2 do
|
1738
|
+
proc {|x| x.to_i }
|
1739
|
+
end
|
1623
1740
|
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1741
|
+
fixture :mapping do
|
1742
|
+
|proc1, proc2|
|
1743
|
+
dp = K8::DefaultPatterns.new
|
1744
|
+
dp.register('id', '\d+', &proc1)
|
1745
|
+
dp.register(/_id\z/, '\d+', &proc2)
|
1746
|
+
K8::ActionMapping.new([
|
1747
|
+
['/api', [
|
1748
|
+
['/books', BooksAction],
|
1749
|
+
['/books/{book_id}', BookCommentsAction],
|
1750
|
+
]],
|
1751
|
+
], default_patterns: dp)
|
1752
|
+
end
|
1636
1753
|
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
ok {
|
1754
|
+
spec "[!6f3vl] compiles urlpath mapping." do
|
1755
|
+
|mapping|
|
1756
|
+
mapping.instance_exec(self) do |_|
|
1757
|
+
_.ok {@urlpath_rexp}.is_a?(Regexp)
|
1758
|
+
_.ok {@urlpath_rexp} == Regexp.compile('
|
1759
|
+
\A/api
|
1760
|
+
(?: /books
|
1761
|
+
(?: /\d+(\z) | /\d+/edit(\z) )
|
1762
|
+
| /books/\d+
|
1763
|
+
(?: /comments(\z) | /comments/\d+(\z) )
|
1764
|
+
)
|
1765
|
+
\z'.gsub(/\s/, ''))
|
1766
|
+
_.ok {@fixed_endpoints.keys} == ["/api/books/", "/api/books/new"]
|
1767
|
+
_.ok {@variable_endpoints.map{|x| x[0..2] }} == [
|
1768
|
+
["/api/books/{id}", BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
|
1769
|
+
["/api/books/{id}/edit", BooksAction, {:GET=>:do_edit}],
|
1770
|
+
["/api/books/{book_id}/comments", BookCommentsAction, {:GET=>:do_comments}],
|
1771
|
+
["/api/books/{book_id}/comments/{comment_id}", BookCommentsAction, {:GET=>:do_comment}],
|
1772
|
+
]
|
1641
1773
|
end
|
1774
|
+
end
|
1642
1775
|
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
ok {
|
1776
|
+
spec "[!w45ad] can compile nested array." do
|
1777
|
+
|mapping, proc1, proc2|
|
1778
|
+
mapping.instance_exec(self) do |_|
|
1779
|
+
_.ok {@urlpath_rexp} == Regexp.compile('
|
1780
|
+
\A /api
|
1781
|
+
(?: /books
|
1782
|
+
(?: /\d+(\z) | /\d+/edit(\z) )
|
1783
|
+
| /books/\d+
|
1784
|
+
(?: /comments(\z) | /comments/\d+(\z) )
|
1785
|
+
)
|
1786
|
+
\z'.gsub(/\s/, ''))
|
1787
|
+
_.ok {@variable_endpoints} == [
|
1788
|
+
["/api/books/{id}",
|
1789
|
+
BooksAction,
|
1790
|
+
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
1791
|
+
/\A\/api\/books\/(\d+)\z/,
|
1792
|
+
["id"], [proc1], (11..-1),
|
1793
|
+
],
|
1794
|
+
["/api/books/{id}/edit",
|
1795
|
+
BooksAction,
|
1796
|
+
{:GET=>:do_edit},
|
1797
|
+
/\A\/api\/books\/(\d+)\/edit\z/,
|
1798
|
+
["id"], [proc1], (11..-6),
|
1799
|
+
],
|
1800
|
+
["/api/books/{book_id}/comments",
|
1801
|
+
BookCommentsAction,
|
1802
|
+
{:GET=>:do_comments},
|
1803
|
+
/\A\/api\/books\/(\d+)\/comments\z/,
|
1804
|
+
["book_id"], [proc2], (11..-10),
|
1805
|
+
],
|
1806
|
+
["/api/books/{book_id}/comments/{comment_id}",
|
1807
|
+
BookCommentsAction,
|
1808
|
+
{:GET=>:do_comment},
|
1809
|
+
/\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
|
1810
|
+
["book_id", "comment_id"], [proc2, proc2], nil,
|
1811
|
+
],
|
1812
|
+
]
|
1813
|
+
_.ok {@fixed_endpoints} == {
|
1814
|
+
"/api/books/" =>["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1815
|
+
"/api/books/new"=>["/api/books/new", BooksAction, {:GET=>:do_new}],
|
1816
|
+
}
|
1647
1817
|
end
|
1818
|
+
end
|
1648
1819
|
|
1820
|
+
spec "[!z2iax] classifies urlpath contains any parameter as variable one." do
|
1821
|
+
|mapping, proc1, proc2|
|
1822
|
+
mapping.instance_exec(self) do |_|
|
1823
|
+
_.ok {@variable_endpoints} == [
|
1824
|
+
["/api/books/{id}",
|
1825
|
+
BooksAction,
|
1826
|
+
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
1827
|
+
/\A\/api\/books\/(\d+)\z/,
|
1828
|
+
["id"], [proc1], (11..-1),
|
1829
|
+
],
|
1830
|
+
["/api/books/{id}/edit",
|
1831
|
+
BooksAction,
|
1832
|
+
{:GET=>:do_edit},
|
1833
|
+
/\A\/api\/books\/(\d+)\/edit\z/,
|
1834
|
+
["id"], [proc1], (11..-6),
|
1835
|
+
],
|
1836
|
+
["/api/books/{book_id}/comments",
|
1837
|
+
BookCommentsAction,
|
1838
|
+
{:GET=>:do_comments},
|
1839
|
+
/\A\/api\/books\/(\d+)\/comments\z/,
|
1840
|
+
["book_id"], [proc2], (11..-10),
|
1841
|
+
],
|
1842
|
+
["/api/books/{book_id}/comments/{comment_id}",
|
1843
|
+
BookCommentsAction,
|
1844
|
+
{:GET=>:do_comment},
|
1845
|
+
/\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
|
1846
|
+
["book_id", "comment_id"], [proc2, proc2], nil,
|
1847
|
+
],
|
1848
|
+
]
|
1849
|
+
end
|
1649
1850
|
end
|
1650
1851
|
|
1651
|
-
spec "[!
|
1852
|
+
spec "[!rvdes] classifies urlpath contains no parameters as fixed one." do
|
1652
1853
|
|mapping|
|
1653
|
-
|
1654
|
-
|
1854
|
+
mapping.instance_exec(self) do |_|
|
1855
|
+
_.ok {@fixed_endpoints} == {
|
1856
|
+
"/api/books/" => ["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1857
|
+
"/api/books/new" => ["/api/books/new", BooksAction, {:GET=>:do_new}],
|
1858
|
+
}
|
1859
|
+
end
|
1655
1860
|
end
|
1656
1861
|
|
1657
|
-
spec "[!
|
1862
|
+
spec "[!6xwhq] builds action infos for each action methods." do
|
1863
|
+
class Ex_6xwhq < K8::Action
|
1864
|
+
mapping '', :GET=>:do_index, :POST=>:do_create
|
1865
|
+
mapping '/{id}', :GET=>:do_show, :PUT=>:do_update
|
1866
|
+
def do_index; end
|
1867
|
+
def do_create; end
|
1868
|
+
def do_show(id); end
|
1869
|
+
def do_update(id); end
|
1870
|
+
end
|
1871
|
+
ok {Ex_6xwhq[:do_create]} == nil
|
1872
|
+
ok {Ex_6xwhq[:do_update]} == nil
|
1873
|
+
#
|
1874
|
+
K8::ActionMapping.new([
|
1875
|
+
['/test', [
|
1876
|
+
['/example4', Ex_6xwhq],
|
1877
|
+
]],
|
1878
|
+
])
|
1879
|
+
#
|
1880
|
+
ok {Ex_6xwhq[:do_create]} != nil
|
1881
|
+
ok {Ex_6xwhq[:do_create].method} == :POST
|
1882
|
+
ok {Ex_6xwhq[:do_create].urlpath} == '/test/example4'
|
1883
|
+
ok {Ex_6xwhq[:do_update]} != nil
|
1884
|
+
ok {Ex_6xwhq[:do_update].method} == :PUT
|
1885
|
+
ok {Ex_6xwhq[:do_update].urlpath(123)} == '/test/example4/123'
|
1886
|
+
end
|
1887
|
+
|
1888
|
+
spec "[!wd2eb] accepts subclass of Action class." do
|
1889
|
+
_, proc1 = K8::DEFAULT_PATTERNS.lookup('id')
|
1890
|
+
_, proc2 = K8::DEFAULT_PATTERNS.lookup('book_id')
|
1891
|
+
mapping = K8::ActionMapping.new([
|
1892
|
+
['/api/books', BooksAction],
|
1893
|
+
['/api/books/{book_id}', BookCommentsAction],
|
1894
|
+
])
|
1895
|
+
mapping.instance_exec(self) do |_|
|
1896
|
+
_.ok {@urlpath_rexp} == Regexp.compile('
|
1897
|
+
\A (?: /api/books
|
1898
|
+
(?: /\d+(\z) | /\d+/edit(\z) )
|
1899
|
+
| /api/books/\d+
|
1900
|
+
(?: /comments(\z) | /comments/\d+(\z) )
|
1901
|
+
)
|
1902
|
+
\z'.gsub(/\s/, ''))
|
1903
|
+
_.ok {@fixed_endpoints} == {
|
1904
|
+
"/api/books/" =>["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1905
|
+
"/api/books/new"=>["/api/books/new", BooksAction, {:GET=>:do_new}],
|
1906
|
+
}
|
1907
|
+
_.ok {@variable_endpoints} == [
|
1908
|
+
["/api/books/{id}",
|
1909
|
+
BooksAction,
|
1910
|
+
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
1911
|
+
/\A\/api\/books\/(\d+)\z/,
|
1912
|
+
["id"], [proc1], (11..-1),
|
1913
|
+
],
|
1914
|
+
["/api/books/{id}/edit",
|
1915
|
+
BooksAction,
|
1916
|
+
{:GET=>:do_edit},
|
1917
|
+
/\A\/api\/books\/(\d+)\/edit\z/,
|
1918
|
+
["id"], [proc1], (11..-6),
|
1919
|
+
],
|
1920
|
+
["/api/books/{book_id}/comments",
|
1921
|
+
BookCommentsAction,
|
1922
|
+
{:GET=>:do_comments},
|
1923
|
+
/\A\/api\/books\/(\d+)\/comments\z/,
|
1924
|
+
["book_id"], [proc2], (11..-10),
|
1925
|
+
],
|
1926
|
+
["/api/books/{book_id}/comments/{comment_id}",
|
1927
|
+
BookCommentsAction,
|
1928
|
+
{:GET=>:do_comment},
|
1929
|
+
/\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
|
1930
|
+
["book_id", "comment_id"], [proc2, proc2], nil,
|
1931
|
+
],
|
1932
|
+
]
|
1933
|
+
end
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
spec "[!ue766] raises error when action method is not defined in action class." do
|
1658
1937
|
|mapping|
|
1659
1938
|
class ExampleAction3 < K8::Action
|
1660
1939
|
mapping '', :GET=>:do_index, :POST=>:do_create
|
1661
1940
|
def do_index; end
|
1662
1941
|
end
|
1663
|
-
pr = proc {
|
1942
|
+
pr = proc { K8::ActionMapping.new([['/example3', ExampleAction3]]) }
|
1664
1943
|
expected_msg = ":POST=>:do_create: unknown action method in ExampleAction3."
|
1665
1944
|
ok {pr}.raise?(K8::UnknownActionMethodError, expected_msg)
|
1666
1945
|
end
|
1667
1946
|
|
1668
|
-
spec "[!
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
mapping.mount '/admin', [
|
1686
|
-
['/books', AdminBooksAction],
|
1687
|
-
]
|
1947
|
+
spec "[!l2kz5] requires library when filepath and classname specified." do
|
1948
|
+
dirname = "test_l2kz5"
|
1949
|
+
filename = dirname + "/sample.rb"
|
1950
|
+
content = <<-END
|
1951
|
+
require 'keight'
|
1952
|
+
module Ex_l2kz5
|
1953
|
+
class Example_l2kz5 < K8::Action
|
1954
|
+
mapping '', :GET=>:do_index
|
1955
|
+
mapping '/{id}', :GET=>:do_show
|
1956
|
+
def do_index; end
|
1957
|
+
def do_show(id); end
|
1958
|
+
end
|
1959
|
+
end
|
1960
|
+
END
|
1961
|
+
Dir.mkdir(dirname) unless File.directory?(dirname)
|
1962
|
+
File.open(filename, 'w') {|f| f << content }
|
1963
|
+
at_end { File.unlink filename; Dir.rmdir dirname }
|
1688
1964
|
#
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1965
|
+
_, proc1 = K8::DEFAULT_PATTERNS.lookup('id')
|
1966
|
+
_, proc2 = K8::DEFAULT_PATTERNS.lookup('book_id')
|
1967
|
+
mapping = K8::ActionMapping.new([
|
1968
|
+
['/api/example', './test_l2kz5/sample:Ex_l2kz5::Example_l2kz5'],
|
1969
|
+
])
|
1970
|
+
mapping.instance_exec(self) do |_|
|
1971
|
+
_.ok {@fixed_endpoints} == {
|
1972
|
+
"/api/example"=>["/api/example", Ex_l2kz5::Example_l2kz5, {:GET=>:do_index}],
|
1973
|
+
}
|
1974
|
+
_.ok {@variable_endpoints} == [
|
1975
|
+
["/api/example/{id}", Ex_l2kz5::Example_l2kz5, {:GET=>:do_show}, /\A\/api\/example\/(\d+)\z/, ["id"], [proc1], (13..-1)],
|
1976
|
+
]
|
1692
1977
|
end
|
1693
|
-
ok {arr[0]} == [:enter, "", "/api", [["/books", BooksAction], ["/books/{book_id}/comments", BookCommentsAction]], nil]
|
1694
|
-
ok {arr[1]} == [:enter, "/api", "/books", BooksAction, nil]
|
1695
|
-
ok {arr[2]} == [:map, "/api/books", "/", BooksAction, {:GET=>:do_index, :POST=>:do_create}]
|
1696
|
-
ok {arr[3]} == [:map, "/api/books", "/new", BooksAction, {:GET=>:do_new}]
|
1697
|
-
ok {arr[4]} == [:map, "/api/books", "/{id}", BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}]
|
1698
|
-
ok {arr[5]} == [:map, "/api/books", "/{id}/edit", BooksAction, {:GET=>:do_edit}]
|
1699
|
-
ok {arr[6]} == [:exit, "/api", "/books", BooksAction, nil]
|
1700
|
-
ok {arr[7]} == [:enter, "/api", "/books/{book_id}/comments", BookCommentsAction, nil]
|
1701
|
-
ok {arr[8]} == [:map, "/api/books/{book_id}/comments", "/comments", BookCommentsAction, {:GET=>:do_comments}]
|
1702
|
-
ok {arr[9]} == [:map, "/api/books/{book_id}/comments", "/comments/{comment_id}", BookCommentsAction, {:GET=>:do_comment}]
|
1703
|
-
ok {arr[10]} == [:exit, "/api", "/books/{book_id}/comments", BookCommentsAction, nil]
|
1704
|
-
ok {arr[11]} == [:exit, "", "/api", [["/books", BooksAction], ["/books/{book_id}/comments", BookCommentsAction]], nil]
|
1705
|
-
ok {arr[12]} == [:enter, "", "/admin", [["/books", AdminBooksAction]], nil]
|
1706
|
-
ok {arr[13]} == [:enter, "/admin", "/books", AdminBooksAction, nil]
|
1707
|
-
ok {arr[14]} == [:map, "/admin/books", "/", AdminBooksAction, {:GET=>:do_index, :POST=>:do_create}]
|
1708
|
-
ok {arr[15]} == [:map, "/admin/books", "/new", AdminBooksAction, {:GET=>:do_new}]
|
1709
|
-
ok {arr[16]} == [:map, "/admin/books", "/{id}", AdminBooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}]
|
1710
|
-
ok {arr[17]} == [:map, "/admin/books", "/{id}/edit", AdminBooksAction, {:GET=>:do_edit}]
|
1711
|
-
ok {arr[18]} == [:exit, "/admin", "/books", AdminBooksAction, nil]
|
1712
|
-
ok {arr[19]} == [:exit, "", "/admin", [["/books", AdminBooksAction]], nil]
|
1713
|
-
ok {arr[20]} == nil
|
1714
1978
|
end
|
1715
1979
|
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
spec "[!driqt] yields full urlpath pattern, action class and action methods." do
|
1722
|
-
mapping = K8::ActionClassMapping.new
|
1723
|
-
mapping.mount '/api', [
|
1724
|
-
['/books', BooksAction],
|
1725
|
-
['/books/{book_id}', BookCommentsAction],
|
1726
|
-
]
|
1727
|
-
mapping.mount '/admin', [
|
1728
|
-
['/books', AdminBooksAction],
|
1729
|
-
]
|
1730
|
-
#
|
1731
|
-
arr = []
|
1732
|
-
mapping.each_mapping do |*args|
|
1733
|
-
arr << args
|
1980
|
+
spec "[!irt5g] raises TypeError when unknown object specified." do
|
1981
|
+
pr = proc do
|
1982
|
+
mapping = K8::ActionMapping.new([
|
1983
|
+
['/api/example', {:GET=>:do_index}],
|
1984
|
+
])
|
1734
1985
|
end
|
1735
|
-
ok {
|
1736
|
-
["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1737
|
-
["/api/books/new", BooksAction, {:GET=>:do_new}],
|
1738
|
-
["/api/books/{id}", BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
|
1739
|
-
["/api/books/{id}/edit", BooksAction, {:GET=>:do_edit}],
|
1740
|
-
#
|
1741
|
-
["/api/books/{book_id}/comments", BookCommentsAction, {:GET=>:do_comments}],
|
1742
|
-
["/api/books/{book_id}/comments/{comment_id}", BookCommentsAction, {:GET=>:do_comment}],
|
1743
|
-
#
|
1744
|
-
["/admin/books/", AdminBooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1745
|
-
["/admin/books/new", AdminBooksAction, {:GET=>:do_new}],
|
1746
|
-
["/admin/books/{id}", AdminBooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
|
1747
|
-
["/admin/books/{id}/edit", AdminBooksAction, {:GET=>:do_edit}],
|
1748
|
-
]
|
1986
|
+
ok {pr}.raise?(TypeError, "Action class or nested array expected, but got {:GET=>:do_index}")
|
1749
1987
|
end
|
1750
1988
|
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1989
|
+
spec "[!bcgc9] skips classes which have only fixed urlpaths." do
|
1990
|
+
klass = Class.new(K8::Action) do
|
1991
|
+
mapping '/', :GET=>:do_index
|
1992
|
+
mapping '/new', :GET=>:do_new
|
1993
|
+
def do_index; end
|
1994
|
+
def do_new; end
|
1995
|
+
end
|
1996
|
+
mapping = K8::ActionMapping.new([
|
1997
|
+
['/api', [
|
1998
|
+
['/books', BooksAction],
|
1999
|
+
['/samples', klass],
|
2000
|
+
['/books/{book_id}', BookCommentsAction],
|
2001
|
+
]],
|
2002
|
+
])
|
2003
|
+
mapping.instance_exec(self) do |_|
|
2004
|
+
_.ok {@urlpath_rexp} == Regexp.compile('
|
2005
|
+
\A /api
|
2006
|
+
(?: /books
|
2007
|
+
(?:/\d+(\z)|/\d+/edit(\z))
|
2008
|
+
| /books/\d+
|
2009
|
+
(?:/comments(\z)|/comments/\d+(\z))
|
2010
|
+
)
|
2011
|
+
\z'.gsub(/\s+/, ''))
|
2012
|
+
_.ok {@fixed_endpoints['/api/samples/']} == ["/api/samples/", klass, {:GET=>:do_index}]
|
2013
|
+
_.ok {@fixed_endpoints['/api/samples/new']} == ["/api/samples/new", klass, {:GET=>:do_new}]
|
2014
|
+
end
|
2015
|
+
end
|
2016
|
+
|
2017
|
+
spec "[!169ad] removes unnecessary grouping." do
|
2018
|
+
klass = Class.new(K8::Action) do
|
2019
|
+
mapping '/{id}', :GET=>:do_show
|
2020
|
+
def do_show(id); end
|
2021
|
+
end
|
2022
|
+
mapping = K8::ActionMapping.new([
|
2023
|
+
['/api', [
|
2024
|
+
['/test', klass],
|
2025
|
+
]],
|
2026
|
+
])
|
2027
|
+
mapping.instance_exec(self) do |_|
|
2028
|
+
#_.ok {@urlpath_rexp} == %r'\A(?:/api(?:/test(?:/\d+(\z))))\z'
|
2029
|
+
_.ok {@urlpath_rexp} == %r'\A/api/test/\d+(\z)\z'
|
2030
|
+
end
|
2031
|
+
end
|
1785
2032
|
|
1786
|
-
fixture :proc_obj2 do
|
1787
|
-
proc {|x| x.to_i }
|
1788
2033
|
end
|
1789
2034
|
|
1790
2035
|
|
1791
|
-
topic '#
|
2036
|
+
topic '#lookup()' do
|
1792
2037
|
|
1793
|
-
|
1794
|
-
|
|
1795
|
-
ok {router.instance_variable_get('@rexp')} != nil
|
1796
|
-
ok {router.instance_variable_get('@list')} != nil
|
1797
|
-
ok {router.instance_variable_get('@dict')} != nil
|
2038
|
+
fixture :proc1 do
|
2039
|
+
proc {|x| x.to_i }
|
1798
2040
|
end
|
1799
2041
|
|
1800
|
-
|
1801
|
-
|
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
2042
|
+
fixture :mapping do
|
2043
|
+
|proc1|
|
2044
|
+
dp = K8::DefaultPatterns.new
|
2045
|
+
dp.register('id', '\d+', &proc1)
|
2046
|
+
dp.register(/_id$/, '\d+', &proc1)
|
2047
|
+
K8::ActionMapping.new([
|
2048
|
+
['/api', [
|
2049
|
+
['/books', BooksAction],
|
2050
|
+
['/books/{book_id}', BookCommentsAction],
|
2051
|
+
]],
|
2052
|
+
], default_patterns: dp, urlpath_cache_size: 3)
|
1807
2053
|
end
|
1808
2054
|
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
|router, proc_obj1|
|
1816
|
-
router.instance_exec(self) do |_|
|
1817
|
-
ret = _compile('/', '\A', '\z', true)
|
1818
|
-
_.ok {ret} == ['\A/\z', [], []]
|
1819
|
-
ret = _compile('/books', '\A', '\z', true)
|
1820
|
-
_.ok {ret} == ['\A/books\z', [], []]
|
1821
|
-
ret = _compile('/books/{id:\d*}', '\A', '\z', true)
|
1822
|
-
_.ok {ret} == ['\A/books/(\d*)\z', ["id"], [nil]]
|
1823
|
-
ret = _compile('/books/{id}/authors/{name}', '\A', '\z', true)
|
1824
|
-
_.ok {ret} == ['\A/books/(\d+)/authors/([^/]+?)\z', ["id", "name"], [proc_obj1, nil]]
|
1825
|
-
end
|
2055
|
+
spec "[!jyxlm] returns action class and methods, parameter names and values." do
|
2056
|
+
|mapping|
|
2057
|
+
tuple = mapping.lookup('/api/books/123')
|
2058
|
+
ok {tuple} == [BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}, ['id'], [123]]
|
2059
|
+
tuple = mapping.lookup('/api/books/123/comments/999')
|
2060
|
+
ok {tuple} == [BookCommentsAction, {:GET=>:do_comment}, ['book_id', 'comment_id'], [123, 999]]
|
1826
2061
|
end
|
1827
2062
|
|
1828
|
-
spec "[!
|
1829
|
-
|
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
2063
|
+
spec "[!j34yh] finds from fixed urlpaths at first." do
|
2064
|
+
|mapping|
|
2065
|
+
mapping.instance_exec(self) do |_|
|
2066
|
+
_.ok {lookup('/books')} == nil
|
2067
|
+
tuple = @fixed_endpoints['/api/books/']
|
2068
|
+
_.ok {tuple} != nil
|
2069
|
+
@fixed_endpoints['/books'] = tuple
|
2070
|
+
expected = [BooksAction, {:GET=>:do_index, :POST=>:do_create}, [], []]
|
2071
|
+
_.ok {lookup('/books')} != nil
|
2072
|
+
_.ok {lookup('/books')} == expected
|
2073
|
+
_.ok {lookup('/api/books/')} == expected
|
1833
2074
|
end
|
1834
2075
|
end
|
1835
2076
|
|
1836
|
-
spec "[!
|
1837
|
-
|
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
2077
|
+
spec "[!95q61] finds from variable urlpath patterns when not found in fixed ones." do
|
2078
|
+
|mapping|
|
2079
|
+
ok {mapping.lookup('/api/books/123')} == \
|
2080
|
+
[
|
2081
|
+
BooksAction,
|
2082
|
+
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
2083
|
+
["id"],
|
2084
|
+
[123],
|
2085
|
+
]
|
2086
|
+
ok {mapping.lookup('/api/books/123/comments/999')} == \
|
2087
|
+
[
|
2088
|
+
BookCommentsAction,
|
2089
|
+
{:GET=>:do_comment},
|
2090
|
+
["book_id", "comment_id"],
|
2091
|
+
[123, 999],
|
2092
|
+
]
|
1844
2093
|
end
|
1845
2094
|
|
1846
|
-
spec "[!
|
1847
|
-
|
|
1848
|
-
|
1849
|
-
ret = _compile('/books/{id:\d*}', '\A', '\z', false)
|
1850
|
-
_.ok {ret} == ['\A/books/\d*\z', ["id"], [nil]]
|
1851
|
-
ret = _compile('/books/{id}/authors/{name}', '\A', '\z', false)
|
1852
|
-
_.ok {ret} == ['\A/books/\d+/authors/[^/]+?\z', ["id", "name"], [proc_obj1, nil]]
|
1853
|
-
end
|
2095
|
+
spec "[!sos5i] returns nil when request path not matched to urlpath patterns." do
|
2096
|
+
|mapping|
|
2097
|
+
ok {mapping.lookup('/api/booking')} == nil
|
1854
2098
|
end
|
1855
2099
|
|
1856
|
-
spec
|
1857
|
-
|
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
2100
|
+
spec "[!1k1k5] converts urlpath param values by converter procs." do
|
2101
|
+
|mapping|
|
2102
|
+
tuple = mapping.lookup('/api/books/123')
|
2103
|
+
ok {tuple[2..3]} == [['id'], [123]]
|
2104
|
+
tuple = mapping.lookup('/api/books/123/comments/999')
|
2105
|
+
ok {tuple[2..3]} == [['book_id', 'comment_id'], [123, 999]]
|
1862
2106
|
end
|
1863
2107
|
|
1864
|
-
spec
|
1865
|
-
|
|
1866
|
-
|
1867
|
-
|
1868
|
-
_.ok {
|
2108
|
+
spec "[!uqwr7] stores result into cache if cache is enabled." do
|
2109
|
+
|mapping|
|
2110
|
+
tuple = mapping.lookup('/api/books/111')
|
2111
|
+
mapping.instance_exec(self) do |_|
|
2112
|
+
_.ok {@urlpath_lru_cache} == {'/api/books/111' => tuple}
|
1869
2113
|
end
|
1870
2114
|
end
|
1871
2115
|
|
1872
|
-
spec
|
1873
|
-
|
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
2116
|
+
spec "[!3ps5g] deletes item from cache when cache size over limit." do
|
2117
|
+
|mapping|
|
2118
|
+
mapping.lookup('/api/books/1')
|
2119
|
+
mapping.lookup('/api/books/2')
|
2120
|
+
mapping.lookup('/api/books/3')
|
2121
|
+
mapping.lookup('/api/books/4')
|
2122
|
+
mapping.lookup('/api/books/5')
|
2123
|
+
mapping.instance_exec(self) do |_|
|
2124
|
+
_.ok {@urlpath_lru_cache.length} == 3
|
1877
2125
|
end
|
1878
2126
|
end
|
1879
2127
|
|
1880
|
-
spec
|
1881
|
-
|
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
2128
|
+
spec "[!uqwr7] uses LRU as cache algorithm." do
|
2129
|
+
|mapping|
|
2130
|
+
mapping.instance_exec(self) do |_|
|
2131
|
+
t1 = lookup('/api/books/1')
|
2132
|
+
t2 = lookup('/api/books/2')
|
2133
|
+
t3 = lookup('/api/books/3')
|
2134
|
+
_.ok {@urlpath_lru_cache.values} == [t1, t2, t3]
|
2135
|
+
t4 = lookup('/api/books/4')
|
2136
|
+
_.ok {@urlpath_lru_cache.values} == [t2, t3, t4]
|
2137
|
+
t5 = lookup('/api/books/5')
|
2138
|
+
_.ok {@urlpath_lru_cache.values} == [t3, t4, t5]
|
2139
|
+
#
|
2140
|
+
lookup('/api/books/4')
|
2141
|
+
_.ok {@urlpath_lru_cache.values} == [t3, t5, t4]
|
2142
|
+
lookup('/api/books/3')
|
2143
|
+
_.ok {@urlpath_lru_cache.values} == [t5, t4, t3]
|
1885
2144
|
end
|
1886
2145
|
end
|
1887
2146
|
|
1888
2147
|
end
|
1889
2148
|
|
1890
2149
|
|
1891
|
-
topic '#
|
1892
|
-
|
1893
|
-
spec "[!956fi] builds regexp object for variable urlpaths (= containing urlpath params)." do
|
1894
|
-
|router|
|
1895
|
-
rexp = router.instance_variable_get('@rexp')
|
1896
|
-
ok {rexp}.is_a?(Regexp)
|
1897
|
-
ok {rexp.source} == '
|
1898
|
-
\A
|
1899
|
-
(?:
|
1900
|
-
/api
|
1901
|
-
(?:
|
1902
|
-
/books
|
1903
|
-
(?: /\d+(\z) | /\d+/edit(\z) )
|
1904
|
-
|
|
1905
|
-
/books/\d+
|
1906
|
-
(?: /comments(\z) | /comments/\d+(\z) )
|
1907
|
-
)
|
1908
|
-
|
|
1909
|
-
/admin
|
1910
|
-
(?:
|
1911
|
-
/books
|
1912
|
-
(?: /\d+(\z) | /\d+/edit(\z) )
|
1913
|
-
)
|
1914
|
-
)
|
1915
|
-
'.gsub(/\s+/, '')
|
1916
|
-
end
|
1917
|
-
|
1918
|
-
spec "[!6tgj5] builds dict of fixed urlpaths (= no urlpath params)." do
|
1919
|
-
|router|
|
1920
|
-
dict = router.instance_variable_get('@dict')
|
1921
|
-
ok {dict} == {
|
1922
|
-
'/api/books/' => [BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1923
|
-
'/api/books/new' => [BooksAction, {:GET=>:do_new}],
|
1924
|
-
'/admin/books/' => [AdminBooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
1925
|
-
'/admin/books/new' => [AdminBooksAction, {:GET=>:do_new}],
|
1926
|
-
}
|
1927
|
-
end
|
2150
|
+
topic '#_compile_urlpath_pat()' do
|
1928
2151
|
|
1929
|
-
|
1930
|
-
|
|
1931
|
-
list = router.instance_variable_get('@list')
|
1932
|
-
ok {list}.is_a?(Array)
|
1933
|
-
ok {list.length} == 6
|
1934
|
-
ok {list[0]} == [
|
1935
|
-
/\A\/api\/books\/(\d+)\z/,
|
1936
|
-
["id"], [proc_obj1],
|
1937
|
-
BooksAction,
|
1938
|
-
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
1939
|
-
]
|
1940
|
-
ok {list[1]} == [
|
1941
|
-
/\A\/api\/books\/(\d+)\/edit\z/,
|
1942
|
-
["id"], [proc_obj1],
|
1943
|
-
BooksAction,
|
1944
|
-
{:GET=>:do_edit},
|
1945
|
-
]
|
1946
|
-
ok {list[2]} == [
|
1947
|
-
/\A\/api\/books\/(\d+)\/comments\z/,
|
1948
|
-
["book_id"], [proc_obj2],
|
1949
|
-
BookCommentsAction,
|
1950
|
-
{:GET=>:do_comments},
|
1951
|
-
]
|
1952
|
-
ok {list[3]} == [
|
1953
|
-
/\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
|
1954
|
-
["book_id", "comment_id"], [proc_obj2, proc_obj2],
|
1955
|
-
BookCommentsAction,
|
1956
|
-
{:GET=>:do_comment},
|
1957
|
-
]
|
1958
|
-
ok {list[4]} == [
|
1959
|
-
/\A\/admin\/books\/(\d+)\z/,
|
1960
|
-
["id"], [proc_obj1],
|
1961
|
-
AdminBooksAction,
|
1962
|
-
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
1963
|
-
]
|
1964
|
-
ok {list[5]} == [
|
1965
|
-
/\A\/admin\/books\/(\d+)\/edit\z/,
|
1966
|
-
["id"], [proc_obj1],
|
1967
|
-
AdminBooksAction,
|
1968
|
-
{:GET=>:do_edit},
|
1969
|
-
]
|
1970
|
-
ok {list[6]} == nil
|
2152
|
+
fixture :proc1 do
|
2153
|
+
proc {|x| x.to_i }
|
1971
2154
|
end
|
1972
2155
|
|
1973
|
-
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|router|
|
1980
|
-
ok {router.find('/api/books/')} == [
|
1981
|
-
BooksAction, {:GET=>:do_index, :POST=>:do_create}, [], [],
|
1982
|
-
]
|
1983
|
-
ok {router.find('/api/books/123')} == [
|
1984
|
-
BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}, ["id"], [123],
|
1985
|
-
]
|
2156
|
+
fixture :default_patterns do
|
2157
|
+
|proc1|
|
2158
|
+
x = K8::DefaultPatterns.new
|
2159
|
+
x.register('id', '\d+', &proc1)
|
2160
|
+
x.register(/_id$/, '\d+', &proc1)
|
2161
|
+
x
|
1986
2162
|
end
|
1987
2163
|
|
1988
|
-
spec "[!
|
1989
|
-
|
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
2164
|
+
spec "[!awfgs] returns regexp string, param names, and converter procs." do
|
2165
|
+
|default_patterns, proc1|
|
2166
|
+
mapping = K8::ActionMapping.new([], default_patterns: default_patterns)
|
2167
|
+
mapping.instance_exec(self) do |_|
|
2168
|
+
#
|
2169
|
+
actual = _compile_urlpath_pat('/books/{id}')
|
2170
|
+
_.ok {actual} == ['/books/\d+', ['id'], [proc1]]
|
2171
|
+
#
|
2172
|
+
actual = _compile_urlpath_pat('/books/{book_id}/comments/{comment_id}')
|
2173
|
+
_.ok {actual} == ['/books/\d+/comments/\d+', ['book_id', 'comment_id'], [proc1, proc1]]
|
2174
|
+
#
|
2175
|
+
actual = _compile_urlpath_pat('/books/{id:[0-9]+}')
|
2176
|
+
_.ok {actual} == ['/books/[0-9]+', ['id'], [nil]]
|
2177
|
+
end
|
1993
2178
|
end
|
1994
2179
|
|
1995
|
-
spec "[!
|
1996
|
-
|
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2180
|
+
spec "[!bi7gr] captures urlpath params when 2nd argument is truthy." do
|
2181
|
+
|default_patterns, proc1|
|
2182
|
+
mapping = K8::ActionMapping.new([], default_patterns: default_patterns)
|
2183
|
+
mapping.instance_exec(self) do |_|
|
2184
|
+
actual = _compile_urlpath_pat('/books/{id}', true)
|
2185
|
+
_.ok {actual} == ['/books/(\d+)', ['id'], [proc1]]
|
2186
|
+
#
|
2187
|
+
actual = _compile_urlpath_pat('/books/{book_id}/comments/{comment_id}', true)
|
2188
|
+
_.ok {actual} == ['/books/(\d+)/comments/(\d+)', ['book_id', 'comment_id'], [proc1, proc1]]
|
2189
|
+
#
|
2190
|
+
actual = _compile_urlpath_pat('/books/{id:[0-9]+}', true)
|
2191
|
+
_.ok {actual} == ['/books/([0-9]+)', ['id'], [nil]]
|
2192
|
+
end
|
2003
2193
|
end
|
2004
2194
|
|
2005
|
-
spec "[!
|
2006
|
-
|
|
2007
|
-
|
2008
|
-
|
2195
|
+
spec "[!mprbx] ex: '/{id:x|y}' -> '/(x|y)', '/{:x|y}' -> '/(?:x|y)'" do
|
2196
|
+
|default_patterns|
|
2197
|
+
mapping = K8::ActionMapping.new([], default_patterns: default_patterns)
|
2198
|
+
mapping.instance_exec(self) do |_|
|
2199
|
+
_.ok {_compile_urlpath_pat('/item/{key:x|y}', true)} == ['/item/(x|y)', ['key'], [nil]]
|
2200
|
+
_.ok {_compile_urlpath_pat('/item/{key:x|y}', false)} == ['/item/(?:x|y)', ['key'], [nil]]
|
2201
|
+
_.ok {_compile_urlpath_pat('/item/{:x|y}', true)} == ['/item/(?:x|y)', [], []]
|
2202
|
+
_.ok {_compile_urlpath_pat('/item/{:x|y}', false)} == ['/item/(?:x|y)', [], []]
|
2203
|
+
end
|
2009
2204
|
end
|
2010
2205
|
|
2011
|
-
spec "[!
|
2012
|
-
|
|
2013
|
-
|
2206
|
+
spec "[!iln54] param names and conveter procs are nil when no urlpath params." do
|
2207
|
+
|default_patterns|
|
2208
|
+
mapping = K8::ActionMapping.new([], default_patterns: default_patterns)
|
2209
|
+
mapping.instance_exec(self) do |_|
|
2210
|
+
actual = _compile_urlpath_pat('/books/new')
|
2211
|
+
_.ok {actual} == ['/books/new', nil, nil]
|
2212
|
+
end
|
2014
2213
|
end
|
2015
2214
|
|
2016
|
-
spec "[!
|
2017
|
-
|
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
_.ok {@urlpath_cache.keys} == ['/api/books/1', '/api/books/2', '/api/books/3']
|
2024
|
-
#
|
2025
|
-
_.ok {find('/api/books/2')} == arr2
|
2026
|
-
_.ok {@urlpath_cache.keys} == ['/api/books/1', '/api/books/3', '/api/books/2']
|
2027
|
-
_.ok {find('/api/books/1')} == arr1
|
2028
|
-
_.ok {@urlpath_cache.keys} == ['/api/books/3', '/api/books/2', '/api/books/1']
|
2215
|
+
spec "[!lhtiz] skips empty param name." do
|
2216
|
+
|default_patterns, proc1|
|
2217
|
+
K8::ActionMapping.new([], default_patterns: default_patterns).instance_exec(self) do |_|
|
2218
|
+
actual = _compile_urlpath_pat('/api/{:\d+}/books')
|
2219
|
+
_.ok {actual} == ['/api/\d+/books', [], []]
|
2220
|
+
actual = _compile_urlpath_pat('/api/{:\d+}/books/{id}')
|
2221
|
+
_.ok {actual} == ['/api/\d+/books/\d+', ['id'], [proc1]]
|
2029
2222
|
end
|
2030
2223
|
end
|
2031
2224
|
|
2032
|
-
spec "[!
|
2033
|
-
|
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
_.ok {
|
2039
|
-
_.ok {find('/api/books/1')} == arr1
|
2040
|
-
_.ok {find('/api/books/2')} == arr2
|
2225
|
+
spec "[!66zas] skips param name starting with '_'." do
|
2226
|
+
|default_patterns, proc1|
|
2227
|
+
K8::ActionMapping.new([], default_patterns: default_patterns).instance_exec(self) do |_|
|
2228
|
+
actual = _compile_urlpath_pat('/api/{_ver:\d+}/books')
|
2229
|
+
_.ok {actual} == ['/api/\d+/books', [], []]
|
2230
|
+
actual = _compile_urlpath_pat('/api/{_ver:\d+}/books/{id}')
|
2231
|
+
_.ok {actual} == ['/api/\d+/books/\d+', ['id'], [proc1]]
|
2041
2232
|
end
|
2042
2233
|
end
|
2043
2234
|
|
2044
|
-
spec "[!
|
2045
|
-
|
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
arr2 = find('/api/books/2')
|
2050
|
-
arr3 = find('/api/books/3')
|
2051
|
-
arr3 = find('/api/books/4')
|
2052
|
-
arr3 = find('/api/books/5')
|
2053
|
-
_.ok {@urlpath_cache.length} == 3
|
2054
|
-
_.ok {@urlpath_cache.keys} == ['/api/books/3', '/api/books/4', '/api/books/5']
|
2235
|
+
spec "[!92jcn] '{' and '}' are available in urlpath param pattern." do
|
2236
|
+
|default_patterns, proc1|
|
2237
|
+
K8::ActionMapping.new([], default_patterns: default_patterns).instance_exec(self) do |_|
|
2238
|
+
actual = _compile_urlpath_pat('/blog/{date:\d{4}-\d{2}-\d{2}}')
|
2239
|
+
_.ok {actual} == ['/blog/\d{4}-\d{2}-\d{2}', ['date'], [nil]]
|
2055
2240
|
end
|
2056
2241
|
end
|
2057
2242
|
|
2058
2243
|
end
|
2059
2244
|
|
2060
2245
|
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
topic K8::ActionRouter do
|
2065
|
-
|
2066
|
-
|
2067
|
-
topic '#initialize()' do
|
2246
|
+
topic '#_require_action_class()' do
|
2068
2247
|
|
2069
|
-
spec "[!
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2248
|
+
spec "[!px9jy] requires file and finds class object." do
|
2249
|
+
filename = 'test_px9jy.rb'
|
2250
|
+
content = "class Ex_px9jy < K8::Action; end\n"
|
2251
|
+
File.open(filename, 'w') {|f| f << content }
|
2252
|
+
at_end { File.unlink filename }
|
2253
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2254
|
+
_.ok { _require_action_class './test_px9jy:Ex_px9jy' } == Ex_px9jy
|
2073
2255
|
end
|
2074
2256
|
end
|
2075
2257
|
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
ret = @default_patterns.lookup('code_hex')
|
2086
|
-
_.ok {ret.length} == 2
|
2087
|
-
_.ok {ret[0]} == '[a-f0-9]+'
|
2088
|
-
_.ok {ret[1]}.is_a?(Proc)
|
2089
|
-
_.ok {ret[1].call('ff')} == 255
|
2258
|
+
spec "[!dlcks] don't rescue LoadError when it is not related to argument." do
|
2259
|
+
filename = 'test_dlcks.rb'
|
2260
|
+
content = "require 'homhomhom'; class Ex_dlcks < K8::Action; end\n"
|
2261
|
+
File.open(filename, 'w') {|f| f << content }
|
2262
|
+
at_end { File.unlink filename }
|
2263
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2264
|
+
pr = proc { _require_action_class './test_dlcks:Ex_dlcks' }
|
2265
|
+
_.ok {pr}.raise?(LoadError, "cannot load such file -- homhomhom")
|
2266
|
+
_.ok {pr.exception.path} == "homhomhom"
|
2090
2267
|
end
|
2091
2268
|
end
|
2092
2269
|
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2098
|
-
|
2099
|
-
router = K8::ActionRouter.new()
|
2100
|
-
router.mount('/api/books', BooksAction)
|
2101
|
-
ret = router.find('/api/books/')
|
2102
|
-
ok {ret} != nil
|
2103
|
-
ok {ret[0]} == BooksAction
|
2270
|
+
spec "[!mngjz] raises error when failed to load file." do
|
2271
|
+
filename = 'test_mngjz.rb'
|
2272
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2273
|
+
pr = proc { _require_action_class './test_mngjz:Ex_mngjz' }
|
2274
|
+
_.ok {pr}.raise?(LoadError, "'./test_mngjz:Ex_mngjz': cannot load './test_mngjz'.")
|
2275
|
+
end
|
2104
2276
|
end
|
2105
2277
|
|
2106
|
-
spec "[!
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
2112
|
-
_.ok {
|
2278
|
+
spec "[!8n6pf] class name may have module prefix name." do
|
2279
|
+
filename = 'test_8n6pf.rb'
|
2280
|
+
content = "module Ex_8n6pf; class Sample < K8::Action; end; end\n"
|
2281
|
+
File.open(filename, 'w') {|f| f << content }
|
2282
|
+
at_end { File.unlink filename }
|
2283
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2284
|
+
_.ok { _require_action_class './test_8n6pf:Ex_8n6pf::Sample' } == Ex_8n6pf::Sample
|
2113
2285
|
end
|
2114
2286
|
end
|
2115
2287
|
|
2116
|
-
|
2117
|
-
|
2288
|
+
spec "[!6lv7l] raises error when action class not found." do
|
2289
|
+
filename = 'test_6lv7l.rb'
|
2290
|
+
content = "module Ex_6lv7l; class Sample_6lv7l < K8::Action; end; end\n"
|
2291
|
+
File.open(filename, 'w') {|f| f << content }
|
2292
|
+
at_end { File.unlink filename }
|
2293
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2294
|
+
pr = proc { _require_action_class './test_6lv7l:Ex_6lv7l::Sample' }
|
2295
|
+
_.ok {pr}.raise?(NameError, "'./test_6lv7l:Ex_6lv7l::Sample': class not found (Ex_6lv7l::Sample).")
|
2296
|
+
end
|
2297
|
+
end
|
2118
2298
|
|
2119
|
-
|
2299
|
+
spec "[!thf7t] raises TypeError when not a class." do
|
2300
|
+
filename = 'test_thf7t.rb'
|
2301
|
+
content = "Ex_thf7t = 'XXX'\n"
|
2302
|
+
File.open(filename, 'w') {|f| f << content }
|
2303
|
+
at_end { File.unlink filename }
|
2304
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2305
|
+
pr = proc { _require_action_class './test_thf7t:Ex_thf7t' }
|
2306
|
+
_.ok {pr}.raise?(TypeError, "'./test_thf7t:Ex_thf7t': class name expected but got \"XXX\".")
|
2307
|
+
end
|
2308
|
+
end
|
2120
2309
|
|
2121
|
-
spec "[!
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
]
|
2127
|
-
|
2128
|
-
|
2129
|
-
]
|
2130
|
-
arr = []
|
2131
|
-
router.each_mapping do |*args|
|
2132
|
-
arr << args
|
2310
|
+
spec "[!yqcgx] raises TypeError when not a subclass of K8::Action." do
|
2311
|
+
filename = 'test_yqcgx.rb'
|
2312
|
+
content = "class Ex_yqcgx; end\n"
|
2313
|
+
File.open(filename, 'w') {|f| f << content }
|
2314
|
+
at_end { File.unlink filename }
|
2315
|
+
K8::ActionMapping.new([]).instance_exec(self) do |_|
|
2316
|
+
pr = proc { _require_action_class './test_yqcgx:Ex_yqcgx' }
|
2317
|
+
_.ok {pr}.raise?(TypeError, "'./test_yqcgx:Ex_yqcgx': expected subclass of K8::Action but not.")
|
2133
2318
|
end
|
2134
|
-
ok {arr} == [
|
2135
|
-
["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
2136
|
-
["/api/books/new", BooksAction, {:GET=>:do_new}],
|
2137
|
-
["/api/books/{id}", BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
|
2138
|
-
["/api/books/{id}/edit", BooksAction, {:GET=>:do_edit}],
|
2139
|
-
["/api/books/{book_id}/comments", BookCommentsAction, {:GET=>:do_comments}],
|
2140
|
-
["/api/books/{book_id}/comments/{comment_id}", BookCommentsAction, {:GET=>:do_comment}],
|
2141
|
-
["/admin/books/", AdminBooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
2142
|
-
["/admin/books/new", AdminBooksAction, {:GET=>:do_new}],
|
2143
|
-
["/admin/books/{id}", AdminBooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
|
2144
|
-
["/admin/books/{id}/edit", AdminBooksAction, {:GET=>:do_edit}],
|
2145
|
-
]
|
2146
2319
|
end
|
2147
2320
|
|
2148
2321
|
end
|
2149
2322
|
|
2150
2323
|
|
2151
|
-
topic '#
|
2324
|
+
topic '#each()' do
|
2152
2325
|
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2160
|
-
_.ok {@finder}.is_a?(K8::ActionFinder)
|
2161
|
-
end
|
2326
|
+
fixture :mapping do
|
2327
|
+
K8::ActionMapping.new([
|
2328
|
+
['/api', [
|
2329
|
+
['/books', BooksAction],
|
2330
|
+
['/books/{book_id}', BookCommentsAction],
|
2331
|
+
]],
|
2332
|
+
])
|
2162
2333
|
end
|
2163
2334
|
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2335
|
+
fixture :expected_tuples do
|
2336
|
+
[
|
2337
|
+
['/api/books/', BooksAction, {:GET=>:do_index, :POST=>:do_create}],
|
2338
|
+
['/api/books/new', BooksAction, {:GET=>:do_new}],
|
2339
|
+
['/api/books/{id}', BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
|
2340
|
+
['/api/books/{id}/edit', BooksAction, {:GET=>:do_edit}],
|
2341
|
+
['/api/books/{book_id}/comments', BookCommentsAction, {:GET=>:do_comments}],
|
2342
|
+
['/api/books/{book_id}/comments/{comment_id}', BookCommentsAction, {:GET=>:do_comment}],
|
2343
|
+
]
|
2171
2344
|
end
|
2172
2345
|
|
2173
|
-
spec "[!
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
ok {ret} == [
|
2185
|
-
AdminBooksAction,
|
2186
|
-
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
2187
|
-
["id"],
|
2188
|
-
[123],
|
2189
|
-
]
|
2346
|
+
spec "[!7ynne] yields each urlpath pattern, action class and action methods." do
|
2347
|
+
|mapping, expected_tuples|
|
2348
|
+
arr = []
|
2349
|
+
mapping.each {|x,y,z| arr << [x, y, z] }
|
2350
|
+
ok {arr} == expected_tuples
|
2351
|
+
end
|
2352
|
+
|
2353
|
+
spec "[!2gwru] returns Enumerator if block is not provided." do
|
2354
|
+
|mapping, expected_tuples|
|
2355
|
+
ok {mapping.each}.is_a?(Enumerator)
|
2356
|
+
ok {mapping.each.map {|x,y,z| [x, y, z] }} == expected_tuples
|
2190
2357
|
end
|
2191
2358
|
|
2192
2359
|
end
|
@@ -2198,17 +2365,17 @@ Oktest.scope do
|
|
2198
2365
|
topic K8::RackApplication do
|
2199
2366
|
|
2200
2367
|
fixture :app do
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2368
|
+
K8::RackApplication.new([
|
2369
|
+
['/api', [
|
2370
|
+
['/books', BooksAction],
|
2371
|
+
]],
|
2372
|
+
])
|
2206
2373
|
end
|
2207
2374
|
|
2208
2375
|
|
2209
2376
|
topic '#initialize()' do
|
2210
2377
|
|
2211
|
-
spec "[!vkp65] mounts urlpath mappings
|
2378
|
+
spec "[!vkp65] mounts urlpath mappings." do
|
2212
2379
|
mapping = [
|
2213
2380
|
['/books' , BooksAction],
|
2214
2381
|
['/books/{id}/comments' , BookCommentsAction],
|
@@ -2247,78 +2414,13 @@ Oktest.scope do
|
|
2247
2414
|
end
|
2248
2415
|
|
2249
2416
|
|
2250
|
-
topic '#
|
2251
|
-
|
2252
|
-
spec "[!i51id] registers '\d+' as default pattern of param 'id' or /_id\z/." do
|
2253
|
-
|app|
|
2254
|
-
app.instance_exec(self) do |_|
|
2255
|
-
pat, proc_ = @router.default_patterns.lookup('id')
|
2256
|
-
_.ok {pat} == '\d+'
|
2257
|
-
_.ok {proc_.call("123")} == 123
|
2258
|
-
pat, proc_ = @router.default_patterns.lookup('book_id')
|
2259
|
-
_.ok {pat} == '\d+'
|
2260
|
-
_.ok {proc_.call("123")} == 123
|
2261
|
-
end
|
2262
|
-
end
|
2263
|
-
|
2264
|
-
spec "[!2g08b] registers '(?:\.\w+)?' as default pattern of param 'ext'." do
|
2265
|
-
|app|
|
2266
|
-
app.instance_exec(self) do |_|
|
2267
|
-
pat, proc_ = @router.default_patterns.lookup('ext')
|
2268
|
-
_.ok {pat} == '(?:\.\w+)?'
|
2269
|
-
_.ok {proc_} == nil
|
2270
|
-
end
|
2271
|
-
end
|
2272
|
-
|
2273
|
-
spec "[!8x5mp] registers '\d\d\d\d-\d\d-\d\d' as default pattern of param 'date' or /_date\z/." do
|
2274
|
-
|app|
|
2275
|
-
app.instance_exec(self) do |_|
|
2276
|
-
pat, proc_ = @router.default_patterns.lookup('date')
|
2277
|
-
_.ok {pat} == '\d\d\d\d-\d\d-\d\d'
|
2278
|
-
_.ok {proc_.call("2014-12-24")} == Date.new(2014, 12, 24)
|
2279
|
-
pat, proc_ = @router.default_patterns.lookup('birth_date')
|
2280
|
-
_.ok {pat} == '\d\d\d\d-\d\d-\d\d'
|
2281
|
-
_.ok {proc_.call("2015-02-14")} == Date.new(2015, 2, 14)
|
2282
|
-
end
|
2283
|
-
end
|
2284
|
-
|
2285
|
-
spec "[!wg9vl] raises 404 error when invalid date (such as 2012-02-30)." do
|
2286
|
-
|app|
|
2287
|
-
app.instance_exec(self) do |_|
|
2288
|
-
pat, proc_ = @router.default_patterns.lookup('date')
|
2289
|
-
pr = proc { proc_.call('2012-02-30') }
|
2290
|
-
_.ok {pr}.raise?(K8::HttpException, "2012-02-30: invalid date.")
|
2291
|
-
_.ok {pr.exception.status_code} == 404
|
2292
|
-
end
|
2293
|
-
end
|
2294
|
-
|
2295
|
-
end
|
2296
|
-
|
2297
|
-
|
2298
|
-
topic '#mount()' do
|
2299
|
-
|
2300
|
-
spec "[!zwva6] mounts action class to urlpath pattern." do
|
2301
|
-
|app|
|
2302
|
-
app.mount('/admin/books', AdminBooksAction)
|
2303
|
-
ret = app.find('/admin/books/123')
|
2304
|
-
ok {ret} == [
|
2305
|
-
AdminBooksAction,
|
2306
|
-
{:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
|
2307
|
-
["id"],
|
2308
|
-
[123],
|
2309
|
-
]
|
2310
|
-
end
|
2311
|
-
|
2312
|
-
end
|
2313
|
-
|
2314
|
-
|
2315
|
-
topic '#find()' do
|
2417
|
+
topic '#lookup()' do
|
2316
2418
|
|
2317
2419
|
spec "[!o0rnr] returns action class, action methods, urlpath names and values." do
|
2318
2420
|
|app|
|
2319
|
-
ret = app.
|
2421
|
+
ret = app.lookup('/api/books/')
|
2320
2422
|
ok {ret} == [BooksAction, {:GET=>:do_index, :POST=>:do_create}, [], []]
|
2321
|
-
ret = app.
|
2423
|
+
ret = app.lookup('/api/books/123')
|
2322
2424
|
ok {ret} == [BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}, ["id"], [123]]
|
2323
2425
|
end
|
2324
2426
|
|
@@ -2348,6 +2450,50 @@ Oktest.scope do
|
|
2348
2450
|
ok {body} == ["<index>"]
|
2349
2451
|
end
|
2350
2452
|
|
2453
|
+
spec "[!eb2ms] returns 301 when urlpath not found but found with tailing '/'." do
|
2454
|
+
|app|
|
2455
|
+
env = new_env("GET", "/api/books")
|
2456
|
+
status, headers, body = app.call(env)
|
2457
|
+
ok {status} == 301
|
2458
|
+
ok {headers['Location']} == "/api/books/"
|
2459
|
+
ok {body} == []
|
2460
|
+
end
|
2461
|
+
|
2462
|
+
spec "[!02dow] returns 301 when urlpath not found but found without tailing '/'." do
|
2463
|
+
|app|
|
2464
|
+
env = new_env("GET", "/api/books/123/")
|
2465
|
+
status, headers, body = app.call(env)
|
2466
|
+
ok {status} == 301
|
2467
|
+
ok {headers['Location']} == "/api/books/123"
|
2468
|
+
ok {body} == []
|
2469
|
+
end
|
2470
|
+
|
2471
|
+
spec "[!2a9c9] adds query string to 'Location' header." do
|
2472
|
+
|app|
|
2473
|
+
env = new_env("GET", "/api/books", query: 'x=1&y=2')
|
2474
|
+
status, headers, body = app.call(env)
|
2475
|
+
ok {status} == 301
|
2476
|
+
ok {headers['Location']} == "/api/books/?x=1&y=2"
|
2477
|
+
#
|
2478
|
+
env = new_env("GET", "/api/books/123/", query: 'x=3&y=4')
|
2479
|
+
status, headers, body = app.call(env)
|
2480
|
+
ok {status} == 301
|
2481
|
+
ok {headers['Location']} == "/api/books/123?x=3&y=4"
|
2482
|
+
end
|
2483
|
+
|
2484
|
+
spec "[!vz07j] redirects only when request method is GET or HEAD." do
|
2485
|
+
|app|
|
2486
|
+
env = new_env("HEAD", "/api/books")
|
2487
|
+
status, headers, body = app.call(env)
|
2488
|
+
ok {status} == 301
|
2489
|
+
ok {headers['Location']} == "/api/books/"
|
2490
|
+
#
|
2491
|
+
env = new_env("POST", "/api/books")
|
2492
|
+
status, headers, body = app.call(env)
|
2493
|
+
ok {status} == 404
|
2494
|
+
ok {headers['Location']} == nil
|
2495
|
+
end
|
2496
|
+
|
2351
2497
|
end
|
2352
2498
|
|
2353
2499
|
|
@@ -2864,307 +3010,6 @@ Oktest.scope do
|
|
2864
3010
|
end
|
2865
3011
|
|
2866
3012
|
|
2867
|
-
topic K8::Mock do
|
2868
|
-
|
2869
|
-
|
2870
|
-
topic '.new_env()' do
|
2871
|
-
|
2872
|
-
spec "[!c779l] raises ArgumentError when both form and json are specified." do
|
2873
|
-
pr = proc { K8::Mock.new_env(form: "x=1", json: {"y"=>2}) }
|
2874
|
-
ok {pr}.raise?(ArgumentError, "new_env(): not allowed both 'form' and 'json' at a time.")
|
2875
|
-
end
|
2876
|
-
|
2877
|
-
spec "[!gko8g] 'multipart:' kwarg accepts Hash object (which is converted into multipart data)." do
|
2878
|
-
env = K8::Mock.new_env(multipart: {"a"=>10, "b"=>20})
|
2879
|
-
ok {env['CONTENT_TYPE']} =~ /\Amultipart\/form-data; *boundary=/
|
2880
|
-
env['CONTENT_TYPE'] =~ /\Amultipart\/form-data; *boundary=(.+)/
|
2881
|
-
boundary = $1
|
2882
|
-
cont_len = Integer(env['CONTENT_LENGTH'])
|
2883
|
-
params, files = K8::Util.parse_multipart(env['rack.input'], boundary, cont_len)
|
2884
|
-
ok {params} == {"a"=>"10", "b"=>"20"}
|
2885
|
-
ok {files} == {}
|
2886
|
-
end
|
2887
|
-
|
2888
|
-
end
|
2889
|
-
|
2890
|
-
|
2891
|
-
end
|
2892
|
-
|
2893
|
-
|
2894
|
-
topic K8::Mock::MultipartBuilder do
|
2895
|
-
|
2896
|
-
|
2897
|
-
topic '#initialize()' do
|
2898
|
-
|
2899
|
-
spec "[!ajfgl] sets random string as boundary when boundary is nil." do
|
2900
|
-
arr = []
|
2901
|
-
1000.times do
|
2902
|
-
mp = K8::Mock::MultipartBuilder.new(nil)
|
2903
|
-
ok {mp.boundary} != nil
|
2904
|
-
ok {mp.boundary}.is_a?(String)
|
2905
|
-
arr << mp.boundary
|
2906
|
-
end
|
2907
|
-
ok {arr.sort.uniq.length} == 1000
|
2908
|
-
end
|
2909
|
-
|
2910
|
-
end
|
2911
|
-
|
2912
|
-
|
2913
|
-
topic '#add()' do
|
2914
|
-
|
2915
|
-
spec "[!tp4bk] detects content type from filename when filename is not nil." do
|
2916
|
-
mp = K8::Mock::MultipartBuilder.new
|
2917
|
-
mp.add("name1", "value1")
|
2918
|
-
mp.add("name2", "value2", "foo.csv")
|
2919
|
-
mp.add("name3", "value3", "bar.csv", "text/plain")
|
2920
|
-
ok {mp.instance_variable_get('@params')} == [
|
2921
|
-
["name1", "value1", nil, nil],
|
2922
|
-
["name2", "value2", "foo.csv", "text/comma-separated-values"],
|
2923
|
-
["name3", "value3", "bar.csv", "text/plain"],
|
2924
|
-
]
|
2925
|
-
end
|
2926
|
-
|
2927
|
-
end
|
2928
|
-
|
2929
|
-
|
2930
|
-
topic '#add_file()' do
|
2931
|
-
|
2932
|
-
fixture :data_dir do
|
2933
|
-
File.join(File.dirname(__FILE__), 'data')
|
2934
|
-
end
|
2935
|
-
|
2936
|
-
fixture :filename1 do |data_dir|
|
2937
|
-
File.join(data_dir, 'example1.png')
|
2938
|
-
end
|
2939
|
-
|
2940
|
-
fixture :filename2 do |data_dir|
|
2941
|
-
File.join(data_dir, 'example1.jpg')
|
2942
|
-
end
|
2943
|
-
|
2944
|
-
fixture :multipart_data do |data_dir|
|
2945
|
-
fname = File.join(data_dir, 'multipart.form')
|
2946
|
-
File.open(fname, 'rb') {|f| f.read }
|
2947
|
-
end
|
2948
|
-
|
2949
|
-
|
2950
|
-
spec "[!uafqa] detects content type from filename when content type is not provided." do
|
2951
|
-
|filename1, filename2|
|
2952
|
-
file1 = File.open(filename1)
|
2953
|
-
file2 = File.open(filename2)
|
2954
|
-
at_end { [file1, file2].each {|f| f.close() unless f.closed? } }
|
2955
|
-
mp = K8::Mock::MultipartBuilder.new
|
2956
|
-
mp.add_file('image1', file1)
|
2957
|
-
mp.add_file('image2', file2)
|
2958
|
-
mp.instance_exec(self) do |_|
|
2959
|
-
_.ok {@params[0][2]} == "example1.png"
|
2960
|
-
_.ok {@params[0][3]} == "image/png"
|
2961
|
-
_.ok {@params[1][2]} == "example1.jpg"
|
2962
|
-
_.ok {@params[1][3]} == "image/jpeg"
|
2963
|
-
end
|
2964
|
-
end
|
2965
|
-
|
2966
|
-
spec "[!b5811] reads file content and adds it as param value." do
|
2967
|
-
|filename1, filename2, multipart_data|
|
2968
|
-
file1 = File.open(filename1)
|
2969
|
-
file2 = File.open(filename2)
|
2970
|
-
at_end { [file1, file2].each {|f| f.close() unless f.closed? } }
|
2971
|
-
boundary = '---------------------------68927884511827559971471404947'
|
2972
|
-
mp = K8::Mock::MultipartBuilder.new(boundary)
|
2973
|
-
mp.add('text1', "test1")
|
2974
|
-
mp.add('text2', "日本語\r\nあいうえお\r\n")
|
2975
|
-
mp.add_file('file1', file1)
|
2976
|
-
mp.add_file('file2', file2)
|
2977
|
-
ok {mp.to_s} == multipart_data
|
2978
|
-
end
|
2979
|
-
|
2980
|
-
spec "[!36bsu] closes opened file automatically." do
|
2981
|
-
|filename1, filename2, multipart_data|
|
2982
|
-
file1 = File.open(filename1)
|
2983
|
-
file2 = File.open(filename2)
|
2984
|
-
at_end { [file1, file2].each {|f| f.close() unless f.closed? } }
|
2985
|
-
ok {file1.closed?} == false
|
2986
|
-
ok {file2.closed?} == false
|
2987
|
-
mp = K8::Mock::MultipartBuilder.new()
|
2988
|
-
mp.add_file('file1', file1)
|
2989
|
-
mp.add_file('file2', file2)
|
2990
|
-
ok {file1.closed?} == true
|
2991
|
-
ok {file2.closed?} == true
|
2992
|
-
end
|
2993
|
-
|
2994
|
-
end
|
2995
|
-
|
2996
|
-
|
2997
|
-
topic '#to_s()' do
|
2998
|
-
|
2999
|
-
spec "[!61gc4] returns multipart form string." do
|
3000
|
-
mp = K8::Mock::MultipartBuilder.new("abc123")
|
3001
|
-
mp.add("name1", "value1")
|
3002
|
-
mp.add("name2", "value2", "foo.txt", "text/plain")
|
3003
|
-
s = mp.to_s
|
3004
|
-
ok {s} == [
|
3005
|
-
"--abc123\r\n",
|
3006
|
-
"Content-Disposition: form-data; name=\"name1\"\r\n",
|
3007
|
-
"\r\n",
|
3008
|
-
"value1\r\n",
|
3009
|
-
"--abc123\r\n",
|
3010
|
-
"Content-Disposition: form-data; name=\"name2\"; filename=\"foo.txt\"\r\n",
|
3011
|
-
"Content-Type: text/plain\r\n",
|
3012
|
-
"\r\n",
|
3013
|
-
"value2\r\n",
|
3014
|
-
"--abc123--\r\n",
|
3015
|
-
].join()
|
3016
|
-
#
|
3017
|
-
params, files = K8::Util.parse_multipart(StringIO.new(s), "abc123", s.length)
|
3018
|
-
begin
|
3019
|
-
ok {params} == {'name1'=>"value1", 'name2'=>"foo.txt"}
|
3020
|
-
ok {files.keys} == ['name2']
|
3021
|
-
ok {files['name2'].filename} == "foo.txt"
|
3022
|
-
ensure
|
3023
|
-
fpath = files['name2'].tmp_filepath
|
3024
|
-
File.unlink(fpath) if File.exist?(fpath)
|
3025
|
-
end
|
3026
|
-
end
|
3027
|
-
|
3028
|
-
end
|
3029
|
-
|
3030
|
-
|
3031
|
-
end
|
3032
|
-
|
3033
|
-
|
3034
|
-
topic K8::Mock::TestApp do
|
3035
|
-
|
3036
|
-
|
3037
|
-
topic '#request()' do
|
3038
|
-
|
3039
|
-
spec "[!4xpwa] creates env object and calls app with it." do
|
3040
|
-
rackapp = proc {|env|
|
3041
|
-
body = [
|
3042
|
-
"PATH_INFO: #{env['PATH_INFO']}\n",
|
3043
|
-
"QUERY_STRING: #{env['QUERY_STRING']}\n",
|
3044
|
-
"HTTP_COOKIE: #{env['HTTP_COOKIE']}\n",
|
3045
|
-
]
|
3046
|
-
[200, {"Content-Type"=>"text/plain"}, body]
|
3047
|
-
}
|
3048
|
-
http = K8::Mock::TestApp.new(rackapp)
|
3049
|
-
resp = http.GET('/foo', query: {"x"=>123}, cookie: {"k"=>"v"})
|
3050
|
-
ok {resp.status} == 200
|
3051
|
-
ok {resp.headers} == {"Content-Type"=>"text/plain"}
|
3052
|
-
ok {resp.body} == [
|
3053
|
-
"PATH_INFO: /foo\n",
|
3054
|
-
"QUERY_STRING: x=123\n",
|
3055
|
-
"HTTP_COOKIE: k=v\n",
|
3056
|
-
]
|
3057
|
-
end
|
3058
|
-
|
3059
|
-
end
|
3060
|
-
|
3061
|
-
end
|
3062
|
-
|
3063
|
-
|
3064
|
-
topic K8::Mock::TestResponse do
|
3065
|
-
|
3066
|
-
|
3067
|
-
topic '#body_binary' do
|
3068
|
-
|
3069
|
-
spec "[!mb0i4] returns body as binary string." do
|
3070
|
-
resp = K8::Mock::TestResponse.new(200, {}, ["foo", "bar"])
|
3071
|
-
ok {resp.body_binary} == "foobar"
|
3072
|
-
#ok {resp.body_binary.encoding} == Encoding::UTF_8
|
3073
|
-
end
|
3074
|
-
|
3075
|
-
end
|
3076
|
-
|
3077
|
-
|
3078
|
-
topic '#body_text' do
|
3079
|
-
|
3080
|
-
spec "[!rr18d] error when 'Content-Type' header is missing." do
|
3081
|
-
resp = K8::Mock::TestResponse.new(200, {}, ["foo", "bar"])
|
3082
|
-
pr = proc { resp.body_text }
|
3083
|
-
ok {pr}.raise?(RuntimeError, "body_text(): missing 'Content-Type' header.")
|
3084
|
-
end
|
3085
|
-
|
3086
|
-
spec "[!dou1n] converts body text according to 'charset' in 'Content-Type' header." do
|
3087
|
-
ctype = "application/json;charset=us-ascii"
|
3088
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Type'=>ctype}, ['{"a":123}'])
|
3089
|
-
ok {resp.body_text} == '{"a":123}'
|
3090
|
-
ok {resp.body_text.encoding} == Encoding::ASCII
|
3091
|
-
end
|
3092
|
-
|
3093
|
-
spec "[!cxje7] assumes charset as 'utf-8' when 'Content-Type' is json." do
|
3094
|
-
ctype = "application/json"
|
3095
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Type'=>ctype}, ['{"a":123}'])
|
3096
|
-
ok {resp.body_text} == '{"a":123}'
|
3097
|
-
ok {resp.body_text.encoding} == Encoding::UTF_8
|
3098
|
-
end
|
3099
|
-
|
3100
|
-
spec "[!n4c71] error when non-json 'Content-Type' header has no 'charset'." do
|
3101
|
-
ctype = "text/plain"
|
3102
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Type'=>ctype}, ["foo", "bar"])
|
3103
|
-
pr = proc { resp.body_text }
|
3104
|
-
ok {pr}.raise?(RuntimeError, "body_text(): missing 'charset' in 'Content-Type' header.")
|
3105
|
-
end
|
3106
|
-
|
3107
|
-
spec "[!vkj9h] returns body as text string, according to 'charset' in 'Content-Type'." do
|
3108
|
-
ctype = "text/plain;charset=utf-8"
|
3109
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Type'=>ctype}, ["foo", "bar"])
|
3110
|
-
ok {resp.body_text} == "foobar"
|
3111
|
-
ok {resp.body_text.encoding} == Encoding::UTF_8
|
3112
|
-
end
|
3113
|
-
|
3114
|
-
end
|
3115
|
-
|
3116
|
-
|
3117
|
-
topic '#body_json' do
|
3118
|
-
|
3119
|
-
spec "[!qnic1] returns Hash object representing JSON string." do
|
3120
|
-
ctype = "application/json"
|
3121
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Type'=>ctype}, ['{"a":123}'])
|
3122
|
-
ok {resp.body_json} == {"a"=>123}
|
3123
|
-
end
|
3124
|
-
|
3125
|
-
end
|
3126
|
-
|
3127
|
-
|
3128
|
-
topic '#content_type' do
|
3129
|
-
|
3130
|
-
spec "[!40hcz] returns 'Content-Type' header value." do
|
3131
|
-
ctype = "application/json"
|
3132
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Type'=>ctype}, ['{"a":123}'])
|
3133
|
-
ok {resp.content_type} == ctype
|
3134
|
-
end
|
3135
|
-
|
3136
|
-
end
|
3137
|
-
|
3138
|
-
|
3139
|
-
topic '#content_length' do
|
3140
|
-
|
3141
|
-
spec "[!5lb19] returns 'Content-Length' header value as integer." do
|
3142
|
-
resp = K8::Mock::TestResponse.new(200, {'Content-Length'=>"0"}, [])
|
3143
|
-
ok {resp.content_length} == 0
|
3144
|
-
ok {resp.content_length}.is_a?(Fixnum)
|
3145
|
-
end
|
3146
|
-
|
3147
|
-
spec "[!qjktz] returns nil when 'Content-Length' is not set." do
|
3148
|
-
resp = K8::Mock::TestResponse.new(200, {}, [])
|
3149
|
-
ok {resp.content_length} == nil
|
3150
|
-
end
|
3151
|
-
|
3152
|
-
end
|
3153
|
-
|
3154
|
-
|
3155
|
-
topic '#location' do
|
3156
|
-
|
3157
|
-
spec "[!8y8lg] returns 'Location' header value." do
|
3158
|
-
resp = K8::Mock::TestResponse.new(200, {'Location'=>'/top'}, [])
|
3159
|
-
ok {resp.location} == "/top"
|
3160
|
-
end
|
3161
|
-
|
3162
|
-
end
|
3163
|
-
|
3164
|
-
|
3165
|
-
end
|
3166
|
-
|
3167
|
-
|
3168
3013
|
end
|
3169
3014
|
|
3170
3015
|
|