keight 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +237 -0
  3. data/README.md +285 -81
  4. data/Rakefile +27 -1
  5. data/bench/benchmarker.rb +2 -2
  6. data/bin/k8rb +112 -305
  7. data/keight.gemspec +4 -6
  8. data/lib/keight.rb +860 -822
  9. data/test/keight_test.rb +1007 -933
  10. data/test/oktest.rb +2 -2
  11. metadata +4 -68
  12. data/CHANGES.txt +0 -64
  13. data/lib/keight/skeleton/.gitignore +0 -10
  14. data/lib/keight/skeleton/README.txt +0 -13
  15. data/lib/keight/skeleton/app/action.rb +0 -106
  16. data/lib/keight/skeleton/app/api/hello.rb +0 -39
  17. data/lib/keight/skeleton/app/form/.keep +0 -0
  18. data/lib/keight/skeleton/app/helper/.keep +0 -0
  19. data/lib/keight/skeleton/app/model.rb +0 -144
  20. data/lib/keight/skeleton/app/model/.keep +0 -0
  21. data/lib/keight/skeleton/app/page/welcome.rb +0 -17
  22. data/lib/keight/skeleton/app/template/_layout.html.eruby +0 -56
  23. data/lib/keight/skeleton/app/template/welcome.html.eruby +0 -6
  24. data/lib/keight/skeleton/app/usecase/.keep +0 -0
  25. data/lib/keight/skeleton/config.rb +0 -46
  26. data/lib/keight/skeleton/config.ru +0 -6
  27. data/lib/keight/skeleton/config/app.rb +0 -29
  28. data/lib/keight/skeleton/config/app_dev.rb +0 -8
  29. data/lib/keight/skeleton/config/app_prod.rb +0 -7
  30. data/lib/keight/skeleton/config/app_stg.rb +0 -5
  31. data/lib/keight/skeleton/config/app_test.rb +0 -8
  32. data/lib/keight/skeleton/config/server_puma.rb +0 -22
  33. data/lib/keight/skeleton/config/server_unicorn.rb +0 -21
  34. data/lib/keight/skeleton/config/urlpath_mapping.rb +0 -16
  35. data/lib/keight/skeleton/index.txt +0 -39
  36. data/lib/keight/skeleton/main.rb +0 -22
  37. data/lib/keight/skeleton/static/lib/.keep +0 -0
  38. data/lib/keight/skeleton/test/api/hello_test.rb +0 -27
  39. data/lib/keight/skeleton/test/test_helper.rb +0 -9
@@ -4,6 +4,7 @@ $LOAD_PATH << "lib" unless $LOAD_PATH.include?("lib")
4
4
  $LOAD_PATH << "test" unless $LOAD_PATH.include?("test")
5
5
 
6
6
  require 'stringio'
7
+ require 'set'
7
8
 
8
9
  require 'rack/test_app'
9
10
  require 'oktest'
@@ -96,6 +97,17 @@ Oktest.scope do
96
97
  return Rack::TestApp.new_env(meth, path, opts)
97
98
  end
98
99
 
100
+ def capture_output
101
+ stdout_bkup, stderr_bkup, $stdout, $stderr = \
102
+ $stdout, $stderr, StringIO.new, StringIO.new
103
+ begin
104
+ yield
105
+ return $stdout.string, $stderr.string
106
+ ensure
107
+ $stdout, $stderr = stdout_bkup, stderr_bkup
108
+ end
109
+ end
110
+
99
111
 
100
112
  topic K8::Util do
101
113
 
@@ -138,12 +150,12 @@ Oktest.scope do
138
150
 
139
151
  topic '.parse_query_string()' do
140
152
 
141
- spec "[!fzt3w] parses query string and returns Hahs object." do
153
+ spec "[!fzt3w] parses query string and returns Hash/dict object." do
142
154
  d = K8::Util.parse_query_string("x=123&y=456&z=")
143
155
  ok {d} == {"x"=>"123", "y"=>"456", "z"=>""}
144
156
  end
145
157
 
146
- spec "[!engr6] returns empty Hash object when query string is empty." do
158
+ spec "[!engr6] returns empty Hash/dict object when query string is empty." do
147
159
  d = K8::Util.parse_query_string("")
148
160
  ok {d} == {}
149
161
  end
@@ -239,7 +251,7 @@ Oktest.scope do
239
251
  end
240
252
 
241
253
 
242
- topic '#http_utc_time()' do
254
+ topic '.http_utc_time()' do
243
255
 
244
256
  spec "[!5k50b] converts Time object into HTTP date format string." do
245
257
  require 'time'
@@ -258,6 +270,279 @@ Oktest.scope do
258
270
  end
259
271
 
260
272
 
273
+ topic 'TemporaryFile' do
274
+
275
+
276
+ topic '.new_path()' do
277
+
278
+ spec "[!hvnzd] generates new temorary filepath under temporary directory." do
279
+ fpath = K8::Util::TemporaryFile.new_path('/var/tmp')
280
+ ok {fpath}.start_with?('/var/tmp/')
281
+ ok {fpath} =~ %r`\A/var/tmp/tmp-\d+\.tmp\z`
282
+ #
283
+ paths = 1000.times.map { K8::Util::TemporaryFile.new_path('/var/tmp') }
284
+ ok {Set.new(paths).size} == 1000
285
+ end
286
+
287
+ spec "[!ulb2e] uses default temporary directory path if tmpdir is not specified." do
288
+ fpath = K8::Util::TemporaryFile.new_path()
289
+ ok {fpath}.start_with?(K8::Util::TemporaryFile::TMPDIR + '/')
290
+ ok {fpath} =~ %r"\A#{Regexp.escape(K8::Util::TemporaryFile::TMPDIR)}/tmp-\d+\.tmp\z"
291
+ end
292
+
293
+ end
294
+
295
+
296
+ topic '#initialize()' do
297
+
298
+ spec "[!ljilm] generates temporary filepath automatically when filepath is not specified." do
299
+ n = 1000
300
+ paths = n.times.map { K8::Util::TemporaryFile.new().path }
301
+ ok {Set.new(paths).size} == n
302
+ end
303
+
304
+ end
305
+
306
+
307
+ topic '#each()' do
308
+
309
+ spec "[!d9suq] opens temporary file with binary mode." do
310
+ tmpf = K8::Util::TemporaryFile.new()
311
+ File.open(tmpf.path, 'wb') {|f| f.write("hogehoge") }
312
+ called = false
313
+ tmpf.each do |s|
314
+ called = true
315
+ ok {s.encoding} == Encoding::BINARY
316
+ end
317
+ ok {called} == true
318
+ end
319
+
320
+ spec "[!68xdj] reads chunk size data from temporary file per iteration." do
321
+ tmpf = K8::Util::TemporaryFile.new(chunk_size: 3)
322
+ File.open(tmpf.path, 'wb') {|f| f.write("hogehoge") }
323
+ i = 0
324
+ tmpf.each do |s|
325
+ case i
326
+ when 0; ok {s} == "hog"
327
+ when 1; ok {s} == "eho"
328
+ when 2; ok {s} == "ge"
329
+ else
330
+ raise "unreachable"
331
+ end
332
+ i += 1
333
+ end
334
+ end
335
+
336
+ spec "[!i0dmd] removes temporary file automatically at end of loop." do
337
+ tmpf = K8::Util::TemporaryFile.new()
338
+ File.open(tmpf.path, 'wb') {|f| f.write("hogehoge") }
339
+ ok {tmpf.path}.file_exist?
340
+ tmpf.each do |s|
341
+ ok {tmpf.path}.file_exist?
342
+ end
343
+ ok {tmpf.path}.NOT.file_exist?
344
+ end
345
+
346
+ spec "[!347an] removes temporary file even if error raised in block." do
347
+ tmpf = K8::Util::TemporaryFile.new()
348
+ File.open(tmpf.path, 'wb') {|f| f.write("hogehoge") }
349
+ ok {tmpf.path}.file_exist?
350
+ pr = proc { tmpf.each {|s| 1/0 } }
351
+ ok {pr}.raise?(ZeroDivisionError)
352
+ ok {tmpf.path}.NOT.file_exist?
353
+ end
354
+
355
+ end
356
+
357
+
358
+ end
359
+
360
+
361
+ topic K8::Util::ShellCommand do
362
+
363
+
364
+ topic '#initialize()' do
365
+
366
+ spec "[!j95pi] takes shell command and input string." do
367
+ command = "psql -AF',' dbname | gzip"
368
+ input = "select * from table1"
369
+ sc = K8::Util::ShellCommand.new(command, input: input)
370
+ ok {sc.command} == command
371
+ ok {sc.input} == input
372
+ #
373
+ sc2 = K8::Util::ShellCommand.new(command)
374
+ ok {sc2.command} == command
375
+ ok {sc2.input} == nil
376
+ end
377
+
378
+ end
379
+
380
+
381
+ topic '#start()' do
382
+
383
+ fixture :sc do
384
+ K8::Util::ShellCommand.new("echo yes")
385
+ end
386
+
387
+ spec "[!66uck] not allowed to start more than once." do |sc|
388
+ ok {proc { sc.start() }}.NOT.raise?(Exception)
389
+ ok {proc { sc.start() }}.raise?(K8::Util::ShellCommandError, 'Already started (comand: "echo yes")')
390
+ end
391
+
392
+ spec "[!9seos] invokes shell command." do |sc|
393
+ ok {sc.process_id} == nil
394
+ sc.start()
395
+ ok {sc.process_id} != nil
396
+ ok {sc.process_id}.is_a?(Fixnum)
397
+ end
398
+
399
+ spec "[!d766y] writes input string if provided to initializer." do |sc|
400
+ input = "a\nb\nc\n"
401
+ sc = K8::Util::ShellCommand.new("cat -n", input: input, chunk_size: 2)
402
+ sc.start()
403
+ buf = ""
404
+ sc.each {|s| buf << s }
405
+ ok {buf} == (" 1 a\n" + \
406
+ " 2 b\n" + \
407
+ " 3 c\n")
408
+ end
409
+
410
+ spec "[!f651x] reads first chunk data." do
411
+ sc = K8::Util::ShellCommand.new("echo abcdefg", chunk_size: 2)
412
+ t = sc.instance_variable_get('@tuple')
413
+ ok {t} == nil
414
+ sc.start()
415
+ t = sc.instance_variable_get('@tuple')
416
+ ok {t} != nil
417
+ sout, serr, waiter, chunk = t
418
+ ok {chunk} == "ab"
419
+ end
420
+
421
+ spec "[!cjstj] raises ShellCommandError when command prints something to stderr." do
422
+ sc = K8::Util::ShellCommand.new("echo abcdefg 1>&2")
423
+ capture_output do
424
+ pr = proc { sc.start() }
425
+ ok {pr}.raise?(K8::Util::ShellCommandError, "abcdefg\n")
426
+ end
427
+ end
428
+
429
+ spec "[!bt12n] saves stdout, stderr, command process, and first chunk data." do
430
+ sc = K8::Util::ShellCommand.new("echo abcdefg", chunk_size: 2)
431
+ sc.start()
432
+ t = sc.instance_variable_get('@tuple')
433
+ ok {t} != nil
434
+ sout, serr, waiter, chunk = t
435
+ ok {sout}.is_a?(IO)
436
+ ok {serr}.is_a?(IO)
437
+ ok {waiter}.is_a?(Process::Waiter)
438
+ ok {chunk} == "ab"
439
+ end
440
+
441
+ spec "[!kgnel] yields callback (if given) when command invoked successfully." do
442
+ sc = K8::Util::ShellCommand.new("echo abcdefg")
443
+ called = false
444
+ sc.start() do
445
+ called = true
446
+ end
447
+ ok {called} == true # called
448
+ #
449
+ capture_output do
450
+ sc = K8::Util::ShellCommand.new("echo abcdefg 1>&2")
451
+ called = false
452
+ sc.start() do
453
+ called = true
454
+ end rescue nil
455
+ ok {called} == false # not called
456
+ end
457
+ end
458
+
459
+ spec "[!2989u] closes both stdout and stderr when error raised." do
460
+ skip_when(true, "hard to test")
461
+ end
462
+
463
+ spec "[!fp98i] returns self." do
464
+ sc = K8::Util::ShellCommand.new("echo abcdefg")
465
+ ok {sc.start()}.same?(sc)
466
+ end
467
+
468
+ end
469
+
470
+
471
+ topic '#each()' do
472
+
473
+ fixture :sc do
474
+ K8::Util::ShellCommand.new("echo yes")
475
+ end
476
+
477
+ spec "[!ssgmm] '#start()' should be called before '#each()'." do |sc|
478
+ ok {proc { sc.each() }}.raise?(K8::Util::ShellCommandError, 'Not started yet (command: "echo yes").')
479
+ end
480
+
481
+ spec "[!vpmbw] yields each chunk data." do |sc|
482
+ sc = K8::Util::ShellCommand.new("echo abcdef", chunk_size: 2)
483
+ sc.start()
484
+ arr = []
485
+ sc.each {|s| arr << s }
486
+ ok {arr} == ["ab", "cd", "ef", "\n"]
487
+ end
488
+
489
+ spec "[!70xdy] logs stderr output." do
490
+ buf = ""
491
+ sout, serr = capture_output do
492
+ sc = K8::Util::ShellCommand.new("time echo abcdef")
493
+ sc.start()
494
+ sc.each {|s| buf << s }
495
+ end
496
+ ok {buf} == "abcdef\n"
497
+ ok {sout} == ""
498
+ ok {serr} != ""
499
+ ok {serr} == ("[ERROR] ShellCommand: \"time echo abcdef\" #-------\n" + \
500
+ " 0.00 real 0.00 user 0.00 sys\n" + \
501
+ "--------------------\n")
502
+ end
503
+
504
+ spec "[!2wll8] closes stdout and stderr, even if error raised." do
505
+ sc = K8::Util::ShellCommand.new("echo abcdef")
506
+ sc.start()
507
+ sout, serr, _, _ = sc.instance_variable_get('@tuple')
508
+ ok {sout.closed?} == false
509
+ ok {serr.closed?} == false
510
+ sc.each {|s| s }
511
+ ok {sout.closed?} == true
512
+ ok {serr.closed?} == true
513
+ #
514
+ sc = K8::Util::ShellCommand.new("time echo abcdef")
515
+ sc.start()
516
+ sout, serr, _, _ = sc.instance_variable_get('@tuple')
517
+ ok {sout.closed?} == false
518
+ ok {serr.closed?} == false
519
+ capture_output do sc.each {|s| s } end
520
+ ok {sout.closed?} == true
521
+ ok {serr.closed?} == true
522
+ end
523
+
524
+ spec "[!0ebq5] calls callback specified at initializer with error object." do
525
+ arg = false
526
+ sc = K8::Util::ShellCommand.new("echo abcdef") {|x| arg = x }
527
+ ok {arg} == false
528
+ sc.start()
529
+ ok {arg} == false
530
+ sc.each {|s| s }
531
+ ok {arg} == nil
532
+ end
533
+
534
+ spec "[!ln8we] returns self." do |sc|
535
+ sc.start()
536
+ ret = sc.each {|s| s }
537
+ ok {ret}.same?(sc)
538
+ end
539
+
540
+ end
541
+
542
+
543
+ end
544
+
545
+
261
546
  end
262
547
 
263
548
 
@@ -325,10 +610,10 @@ Oktest.scope do
325
610
  end
326
611
 
327
612
 
328
- topic K8::Request do
613
+ topic K8::RackRequest do
329
614
 
330
615
  fixture :req do
331
- K8::Request.new(new_env("GET", "/123"))
616
+ K8::RackRequest.new(new_env("GET", "/123"))
332
617
  end
333
618
 
334
619
  fixture :data_dir do
@@ -348,28 +633,28 @@ Oktest.scope do
348
633
 
349
634
  spec "[!yb9k9] sets @env." do
350
635
  env = new_env()
351
- req = K8::Request.new(env)
636
+ req = K8::RackRequest.new(env)
352
637
  ok {req.env}.same?(env)
353
638
  end
354
639
 
355
- spec "[!yo22o] sets @method as Symbol value." do
356
- req1 = K8::Request.new(new_env("GET"))
357
- ok {req1.method} == :GET
358
- req2 = K8::Request.new(new_env("POST"))
359
- ok {req2.method} == :POST
640
+ spec "[!yo22o] sets @meth as Symbol value." do
641
+ req1 = K8::RackRequest.new(new_env("GET"))
642
+ ok {req1.meth} == :GET
643
+ req2 = K8::RackRequest.new(new_env("POST"))
644
+ ok {req2.meth} == :POST
360
645
  end
361
646
 
362
647
  spec "[!twgmi] sets @path." do
363
- req1 = K8::Request.new(new_env("GET", "/123"))
648
+ req1 = K8::RackRequest.new(new_env("GET", "/123"))
364
649
  ok {req1.path} == "/123"
365
650
  end
366
651
 
367
652
  spec "[!ae8ws] uses SCRIPT_NAME as urlpath when PATH_INFO is not provided." do
368
653
  env = new_env("GET", "/123", env: {'SCRIPT_NAME'=>'/index.cgi'})
369
654
  env['PATH_INFO'] = ''
370
- ok {K8::Request.new(env).path} == "/index.cgi"
655
+ ok {K8::RackRequest.new(env).path} == "/index.cgi"
371
656
  env.delete('PATH_INFO')
372
- ok {K8::Request.new(env).path} == "/index.cgi"
657
+ ok {K8::RackRequest.new(env).path} == "/index.cgi"
373
658
  end
374
659
 
375
660
  end
@@ -378,7 +663,7 @@ Oktest.scope do
378
663
  topic '#method()' do
379
664
 
380
665
  fixture :req do
381
- K8::Request.new({"REQUEST_METHOD"=>"POST"})
666
+ K8::RackRequest.new({"REQUEST_METHOD"=>"POST"})
382
667
  end
383
668
 
384
669
  spec "[!084jo] returns current request method when argument is not specified." do
@@ -395,13 +680,32 @@ Oktest.scope do
395
680
  end
396
681
 
397
682
 
683
+ topic '#path_ext()' do
684
+
685
+ spec "[!tf6yz] returns extension of request path such as '.html' or '.json'." do
686
+ req1 = K8::RackRequest.new(new_env("GET", "/api/books/123.html"))
687
+ ok {req1.path_ext} == ".html"
688
+ req2 = K8::RackRequest.new(new_env("GET", "/api/books/123.json"))
689
+ ok {req2.path_ext} == ".json"
690
+ end
691
+
692
+ spec "[!xnurj] returns empty string when no extension." do
693
+ req1 = K8::RackRequest.new(new_env("GET", "/api/books/123"))
694
+ ok {req1.path_ext} == ""
695
+ req2 = K8::RackRequest.new(new_env("GET", "/api/books.d/123"))
696
+ ok {req2.path_ext} == ""
697
+ end
698
+
699
+ end
700
+
701
+
398
702
  topic '#header()' do
399
703
 
400
704
  spec "[!1z7wj] returns http header value from environment." do
401
705
  env = new_env("GET", "/",
402
706
  headers: {'Accept-Encoding'=>'gzip,deflate'},
403
707
  env: {'HTTP_ACCEPT_LANGUAGE'=>'en,ja'})
404
- req = K8::Request.new(env)
708
+ req = K8::RackRequest.new(env)
405
709
  ok {req.header('Accept-Encoding')} == 'gzip,deflate'
406
710
  ok {req.header('Accept-Language')} == 'en,ja'
407
711
  end
@@ -412,7 +716,7 @@ Oktest.scope do
412
716
  topic '#request_method' do
413
717
 
414
718
  spec "[!y8eos] returns env['REQUEST_METHOD'] as string." do
415
- req = K8::Request.new(new_env(:POST, "/"))
719
+ req = K8::RackRequest.new(new_env(:POST, "/"))
416
720
  ok {req.request_method} == "POST"
417
721
  end
418
722
 
@@ -423,9 +727,9 @@ Oktest.scope do
423
727
 
424
728
  spec "[!95g9o] returns env['CONTENT_TYPE']." do
425
729
  ctype = "text/html"
426
- req = K8::Request.new(new_env("GET", "/", env: {'CONTENT_TYPE'=>ctype}))
730
+ req = K8::RackRequest.new(new_env("GET", "/", env: {'CONTENT_TYPE'=>ctype}))
427
731
  ok {req.content_type} == ctype
428
- req = K8::Request.new(new_env("GET", "/", env: {}))
732
+ req = K8::RackRequest.new(new_env("GET", "/", env: {}))
429
733
  ok {req.content_type} == nil
430
734
  end
431
735
 
@@ -435,7 +739,7 @@ Oktest.scope do
435
739
  topic '#content_length' do
436
740
 
437
741
  spec "[!0wbek] returns env['CONTENT_LENGHT'] as integer." do
438
- req = K8::Request.new(new_env("GET", "/", env: {'CONTENT_LENGTH'=>'0'}))
742
+ req = K8::RackRequest.new(new_env("GET", "/", env: {'CONTENT_LENGTH'=>'0'}))
439
743
  ok {req.content_length} == 0
440
744
  req.env.delete('CONTENT_LENGTH')
441
745
  ok {req.content_length} == nil
@@ -448,9 +752,9 @@ Oktest.scope do
448
752
 
449
753
  spec "[!hsgkg] returns true when 'X-Requested-With' header is 'XMLHttpRequest'." do
450
754
  env = new_env("GET", "/", headers: {'X-Requested-With'=>'XMLHttpRequest'})
451
- ok {K8::Request.new(env).xhr?} == true
755
+ ok {K8::RackRequest.new(env).xhr?} == true
452
756
  env = new_env("GET", "/", headers: {})
453
- ok {K8::Request.new(env).xhr?} == false
757
+ ok {K8::RackRequest.new(env).xhr?} == false
454
758
  end
455
759
 
456
760
  end
@@ -462,20 +766,20 @@ Oktest.scope do
462
766
  env = new_env("GET", "/",
463
767
  headers: {'X-Real-IP'=>'192.168.1.23'},
464
768
  env: {'REMOTE_ADDR'=>'192.168.0.1'})
465
- ok {K8::Request.new(env).client_ip_addr} == '192.168.1.23'
769
+ ok {K8::RackRequest.new(env).client_ip_addr} == '192.168.1.23'
466
770
  end
467
771
 
468
772
  spec "[!qdlyl] returns first item of 'X-Forwarded-For' header if provided." do
469
773
  env = new_env("GET", "/",
470
774
  headers: {'X-Forwarded-For'=>'192.168.1.1, 192.168.1.2, 192.168.1.3'},
471
775
  env: {'REMOTE_ADDR'=>'192.168.0.1'})
472
- ok {K8::Request.new(env).client_ip_addr} == '192.168.1.1'
776
+ ok {K8::RackRequest.new(env).client_ip_addr} == '192.168.1.1'
473
777
  end
474
778
 
475
779
  spec "[!8nzjh] returns 'REMOTE_ADDR' if neighter 'X-Real-IP' nor 'X-Forwarded-For' provided." do
476
780
  env = new_env("GET", "/",
477
781
  env: {'REMOTE_ADDR'=>'192.168.0.1'})
478
- ok {K8::Request.new(env).client_ip_addr} == '192.168.0.1'
782
+ ok {K8::RackRequest.new(env).client_ip_addr} == '192.168.0.1'
479
783
  end
480
784
 
481
785
  end
@@ -485,15 +789,15 @@ Oktest.scope do
485
789
 
486
790
  spec "[!jytwy] returns 'https' when env['HTTPS'] is 'on'." do
487
791
  env = new_env("GET", "/", env: {'HTTPS'=>'on'})
488
- ok {K8::Request.new(env).scheme} == 'https'
792
+ ok {K8::RackRequest.new(env).scheme} == 'https'
489
793
  end
490
794
 
491
795
  spec "[!zg8r2] returns env['rack.url_scheme'] ('http' or 'https')." do
492
796
  env = new_env("GET", "/", env: {'HTTPS'=>'off'})
493
797
  env['rack.url_scheme'] = 'http'
494
- ok {K8::Request.new(env).scheme} == 'http'
798
+ ok {K8::RackRequest.new(env).scheme} == 'http'
495
799
  env['rack.url_scheme'] = 'https'
496
- ok {K8::Request.new(env).scheme} == 'https'
800
+ ok {K8::RackRequest.new(env).scheme} == 'https'
497
801
  end
498
802
 
499
803
  end
@@ -501,62 +805,102 @@ Oktest.scope do
501
805
 
502
806
  topic '#params_query' do
503
807
 
504
- spec "[!6ezqw] parses QUERY_STRING and returns it as Hash object." do
808
+ spec "[!6ezqw] parses QUERY_STRING and returns it as Hash/dict object." do
505
809
  qstr = "x=1&y=2"
506
- req = K8::Request.new(new_env("GET", "/", env: {'QUERY_STRING'=>qstr}))
810
+ req = K8::RackRequest.new(new_env("GET", "/", env: {'QUERY_STRING'=>qstr}))
507
811
  ok {req.params_query()} == {'x'=>'1', 'y'=>'2'}
508
812
  end
509
813
 
510
814
  spec "[!o0ws7] unquotes both keys and values." do
511
815
  qstr = "arr%5Bxxx%5D=%3C%3E+%26%3B"
512
- req = K8::Request.new(new_env("GET", "/", env: {'QUERY_STRING'=>qstr}))
816
+ req = K8::RackRequest.new(new_env("GET", "/", env: {'QUERY_STRING'=>qstr}))
513
817
  ok {req.params_query()} == {'arr[xxx]'=>'<> &;'}
514
818
  end
515
819
 
820
+ spec "[!2fhrk] returns same value when called more than once." do
821
+ req = K8::RackRequest.new(new_env("GET", "/", query: "x=1&y=2"))
822
+ val1st = req.params_query()
823
+ val2nd = req.params_query()
824
+ ok {val2nd}.same?(val1st)
825
+ end
826
+
516
827
  end
517
828
 
518
829
 
519
830
  topic '#params_form' do
520
831
 
521
- spec "[!q88w9] raises error when content length is missing." do
522
- env = new_env("POST", "/", form: "x=1")
523
- env['CONTENT_LENGTH'] = nil
524
- req = K8::Request.new(env)
832
+ spec "[!iultp] returns same value when called more than once." do
833
+ req = K8::RackRequest.new(new_env("POST", "/", form: "x=1"))
834
+ val1st = req.params_form
835
+ val2nd = req.params_form
836
+ ok {val2nd}.same?(val1st)
837
+ end
838
+
839
+ spec "[!uq46o] raises 400 error when payload is not form data." do
840
+ |multipart_env|
841
+ env = multipart_env
842
+ req = K8::RackRequest.new(env)
525
843
  pr = proc { req.params_form }
526
- ok {pr}.raise?(K8::HttpException, 'Content-Length header expected.')
844
+ ok {pr}.raise?(K8::HttpException, /^expected form data, but Content-Type header is "multipart\/form-data;boundary=.*".$/)
527
845
  end
528
846
 
529
- spec "[!gi4qq] raises error when content length is invalid." do
847
+ spec "[!puxlr] raises 400 error when content length is too large (> 10MB)." do
530
848
  env = new_env("POST", "/", form: "x=1")
531
- env['CONTENT_LENGTH'] = "abc"
532
- req = K8::Request.new(env)
849
+ env['CONTENT_LENGTH'] = (10*1024*1024 + 1).to_s
850
+ req = K8::RackRequest.new(env)
533
851
  pr = proc { req.params_form }
534
- ok {pr}.raise?(K8::HttpException, 'Content-Length should be an integer.')
852
+ ok {pr}.raise?(K8::HttpException, 'Content-Length is too large (max: 10485760, actual: 10485761).')
535
853
  end
536
854
 
537
- spec "[!59ad2] parses form parameters and returns it as Hash object when form requested." do
855
+ spec "[!59ad2] parses form parameters and returns it as Hash/dict object." do
538
856
  form = "x=1&y=2&arr%5Bxxx%5D=%3C%3E+%26%3B"
539
- req = K8::Request.new(new_env("POST", "/", form: form))
857
+ req = K8::RackRequest.new(new_env("POST", "/", form: form))
540
858
  ok {req.params_form} == {'x'=>'1', 'y'=>'2', 'arr[xxx]'=>'<> &;'}
541
859
  end
542
860
 
543
- spec "[!puxlr] raises error when content length is too long (> 10MB)." do
544
- env = new_env("POST", "/", form: "x=1")
545
- env['CONTENT_LENGTH'] = (10*1024*1024 + 1).to_s
546
- req = K8::Request.new(env)
547
- pr = proc { req.params_form }
548
- ok {pr}.raise?(K8::HttpException, 'Content-Length is too long.')
549
- end
550
-
551
861
  end
552
862
 
553
863
 
554
864
  topic '#params_multipart' do
555
865
 
556
- spec "[!y1jng] parses multipart when multipart form requested." do
866
+ spec "[!gbdxu] returns same values when called more than once." do
867
+ |multipart_env|
868
+ req = K8::RackRequest.new(multipart_env)
869
+ d1, d2 = req.params_multipart
870
+ d3, d4 = req.params_multipart
871
+ ok {d3}.same?(d1)
872
+ ok {d4}.same?(d2)
873
+ end
874
+
875
+ spec "[!ho5ii] raises 400 error when not multipart data." do
876
+ env = new_env("POST", "/", form: "x=1")
877
+ req = K8::RackRequest.new(env)
878
+ pr = proc { req.params_multipart }
879
+ ok {pr}.raise?(K8::HttpException, 'expected multipart data, but Content-Type header is "application/x-www-form-urlencoded".')
880
+ end
881
+
882
+ spec "[!davzs] raises 400 error when boundary is missing." do
883
+ |multipart_env, data_dir|
884
+ env = multipart_env
885
+ env['CONTENT_TYPE'] = env['CONTENT_TYPE'].split(';').first
886
+ req = K8::RackRequest.new(env)
887
+ pr = proc { req.params_multipart }
888
+ ok {pr}.raise?(K8::HttpException, 'bounday attribute of multipart required.')
889
+ end
890
+
891
+ spec "[!mtx6t] raises 400 error when content length of multipart is too large (> 100MB)." do
892
+ |multipart_env|
893
+ env = multipart_env
894
+ env['CONTENT_LENGTH'] = (100*1024*1024 + 1).to_s
895
+ req = K8::RackRequest.new(env)
896
+ pr = proc { req.params_multipart }
897
+ ok {pr}.raise?(K8::HttpException, 'Content-Length is too large (max: 104857600, actual: 104857601).')
898
+ end
899
+
900
+ spec "[!y1jng] parses multipart when multipart data posted." do
557
901
  |multipart_env, data_dir|
558
902
  env = multipart_env
559
- req = K8::Request.new(env)
903
+ req = K8::RackRequest.new(env)
560
904
  form, files = req.params_multipart
561
905
  ok {form} == {
562
906
  "text1" => "test1",
@@ -585,53 +929,110 @@ Oktest.scope do
585
929
 
586
930
  end
587
931
 
588
- spec "[!mtx6t] raises error when content length of multipart is too long (> 100MB)." do
589
- |multipart_env|
590
- env = multipart_env
591
- env['CONTENT_LENGTH'] = (100*1024*1024 + 1).to_s
592
- req = K8::Request.new(env)
593
- pr = proc { req.params_multipart }
594
- ok {pr}.raise?(K8::HttpException, 'Content-Length of multipart is too long.')
595
- end
596
-
597
932
  end
598
933
 
599
934
 
600
935
  topic '#params_json' do
601
936
 
937
+ spec "[!5kwij] returns same value when called more than once." do
938
+ req = K8::RackRequest.new(new_env("POST", "/", json: {"x"=>1}))
939
+ val1st = req.params_json
940
+ val2nd = req.params_json
941
+ ok {val2nd}.same?(val1st)
942
+ end
943
+
944
+ spec "[!qjgfz] raises 400 error when not JSON data." do
945
+ req = K8::RackRequest.new(new_env("POST", "/", form: "x=1"))
946
+ pr = proc { req.params_json }
947
+ ok {pr}.raise?(K8::HttpException, "expected JSON data, but Content-Type header is \"application/x-www-form-urlencoded\".")
948
+ end
949
+
950
+ spec "[!on107] raises error when content length of JSON is too large (> 10MB)." do
951
+ env = new_env("POST", "/", json: {"x"=>1})
952
+ env['CONTENT_LENGTH'] = (10*1024*1024 + 1).to_s
953
+ req = K8::RackRequest.new(env)
954
+ pr = proc { req.params_json }
955
+ ok {pr}.raise?(K8::HttpException, 'Content-Length is too large (max: 10485760, actual: 10485761).')
956
+ end
957
+
602
958
  spec "[!ugik5] parses json data and returns it as hash object when json data is sent." do
603
959
  data = '{"x":1,"y":2,"arr":["a","b","c"]}'
604
- req = K8::Request.new(new_env("POST", "/", json: data))
960
+ req = K8::RackRequest.new(new_env("POST", "/", json: data))
605
961
  ok {req.params_json} == {"x"=>1, "y"=>2, "arr"=>["a", "b", "c"]}
606
962
  end
607
963
 
608
964
  end
609
965
 
610
966
 
967
+ topic '#get_content_length()' do
968
+
969
+ spec "[!q88w9] raises 400 error when content length is missing." do
970
+ env = new_env("POST", "/", form: "x=1")
971
+ env['CONTENT_LENGTH'] = nil
972
+ req = K8::RackRequest.new(env)
973
+ req.instance_exec(self) do |_|
974
+ pr = proc { get_content_length(100) }
975
+ _.ok {pr}.raise?(K8::HttpException, 'Content-Length header expected.')
976
+ end
977
+ end
978
+
979
+ spec "[!ls6ir] raises error when content length is too large." do
980
+ env = new_env("POST", "/", form: "x=1")
981
+ env['CONTENT_LENGTH'] = '101'
982
+ req = K8::RackRequest.new(env)
983
+ req.instance_exec(self) do |_|
984
+ pr = proc { get_content_length(100) }
985
+ _.ok {pr}.raise?(K8::HttpException, 'Content-Length is too large (max: 100, actual: 101).')
986
+ end
987
+ end
988
+
989
+ end
990
+
991
+
992
+ topic '#get_input_stream()' do
993
+
994
+ spec "[!2buc6] returns input stream." do
995
+ env = new_env("POST", "/", form: "x=1")
996
+ req = K8::RackRequest.new(env)
997
+ req.instance_exec(self) do |_|
998
+ _.ok { get_input_stream() }.same?(req.env['rack.input'])
999
+ end
1000
+ end
1001
+
1002
+ end
1003
+
1004
+
611
1005
  topic '#params' do
612
1006
 
613
1007
  spec "[!erlc7] parses QUERY_STRING when request method is GET or HEAD." do
614
1008
  qstr = "a=8&b=9"
615
1009
  form = "x=1&y=2"
616
- req = K8::Request.new(new_env('GET', '/', query: qstr, form: form))
1010
+ req = K8::RackRequest.new(new_env('GET', '/', query: qstr, form: form))
617
1011
  ok {req.params} == {"a"=>"8", "b"=>"9"}
618
1012
  end
619
1013
 
620
- spec "[!cr0zj] parses JSON when content type is 'application/json'." do
621
- qstr = "a=8&b=9"
622
- json = '{"n":123}'
623
- req = K8::Request.new(new_env('POST', '/', query: qstr, json: json))
624
- ok {req.params} == {"n"=>123}
625
- end
626
-
627
1014
  spec "[!j2lno] parses form parameters when content type is 'application/x-www-form-urlencoded'." do
628
1015
  qstr = "a=8&b=9"
629
1016
  form = "x=1&y=2"
630
- req = K8::Request.new(new_env('POST', '/', query: qstr, form: form))
1017
+ req = K8::RackRequest.new(new_env('POST', '/', query: qstr, form: form))
631
1018
  ok {req.params} == {"x"=>"1", "y"=>"2"}
632
1019
  end
633
1020
 
634
- spec "[!4rmn9] parses multipart when content type is 'multipart/form-data'."
1021
+ spec "[!z5w4k] raises error when content type is 'multipart/form-data' (because params_multipart() returns two values)." do
1022
+ env = new_env('POST', '/', form: "x=1")
1023
+ env['CONTENT_TYPE'] = "multipart/form-data"
1024
+ req = K8::RackRequest.new(env)
1025
+ pr = proc { req.params }
1026
+ ok {pr}.raise?(K8::PayloadParseError, "don't use `@req.params' for multipart data; use `@req.params_multipart' instead.")
1027
+ end
1028
+
1029
+ spec "[!td6fw] raises error when content type is 'application/json' (because JSON data can contain non-string values)." do
1030
+ qstr = "a=8&b=9"
1031
+ json = '{"n":123}'
1032
+ req = K8::RackRequest.new(new_env('POST', '/', query: qstr, json: json))
1033
+ pr = proc { req.params }
1034
+ ok {pr}.raise?(K8::PayloadParseError, "use `@req.json' for JSON data instead of `@req.params'.")
1035
+ end
635
1036
 
636
1037
  end
637
1038
 
@@ -639,7 +1040,7 @@ Oktest.scope do
639
1040
  topic '#cookies' do
640
1041
 
641
1042
  spec "[!c9pwr] parses cookie data and returns it as hash object." do
642
- req = K8::Request.new(new_env('POST', '/', cookies: "aaa=homhom; bbb=madmad"))
1043
+ req = K8::RackRequest.new(new_env('POST', '/', cookies: "aaa=homhom; bbb=madmad"))
643
1044
  ok {req.cookies} == {"aaa"=>"homhom", "bbb"=>"madmad"}
644
1045
  end
645
1046
 
@@ -650,7 +1051,7 @@ Oktest.scope do
650
1051
 
651
1052
  spec "[!0jdal] removes uploaded files." do
652
1053
  |multipart_env|
653
- req = K8::Request.new(multipart_env)
1054
+ req = K8::RackRequest.new(multipart_env)
654
1055
  form, files = req.params_multipart
655
1056
  ok {files.empty?} == false
656
1057
  tmpfile1 = files['file1'].tmp_filepath
@@ -669,37 +1070,66 @@ Oktest.scope do
669
1070
  end
670
1071
 
671
1072
 
672
- topic K8::Response do
673
- end
1073
+ topic K8::RackResponse do
674
1074
 
675
1075
 
676
- topic 'K8::REQUEST_CLASS=' do
1076
+ topic '#initialize()' do
677
1077
 
678
- spec "[!7uqb4] changes default request class." do
679
- original = K8::REQUEST_CLASS
680
- begin
681
- K8.REQUEST_CLASS = Array
682
- ok {K8::REQUEST_CLASS} == Array
683
- ensure
684
- K8.REQUEST_CLASS = original
1078
+ spec "[!ehdkl] default status code is 200." do
1079
+ resp = K8::RackResponse.new
1080
+ ok {resp.status_code} == 200
685
1081
  end
1082
+
686
1083
  end
687
1084
 
688
- end
689
1085
 
1086
+ topic '#status_line' do
1087
+
1088
+ spec "[!apy81] returns status line such as '200 OK'." do
1089
+ resp = K8::RackResponse.new
1090
+ ok {resp.status_line} == "200 OK"
1091
+ resp.status_code = 302
1092
+ ok {resp.status_line} == "302 Found"
1093
+ resp.status_code = 404
1094
+ ok {resp.status_line} == "404 Not Found"
1095
+ resp.status_code = 500
1096
+ ok {resp.status_line} == "500 Internal Server Error"
1097
+ end
1098
+
1099
+ end
1100
+
1101
+
1102
+ topic '#set_cookie()' do
1103
+
1104
+ spec "[!58tby] adds 'Set-Cookie' response header." do
1105
+ resp = K8::RackResponse.new
1106
+ resp.set_cookie("hom", "HOMHOM")
1107
+ ok {resp.headers['Set-Cookie']} == "hom=HOMHOM"
1108
+ end
1109
+
1110
+ spec "[!u9w9l] supports multiple cookies." do
1111
+ resp = K8::RackResponse.new
1112
+ resp.set_cookie("foo", "FOO")
1113
+ resp.set_cookie("bar", "BAR")
1114
+ resp.set_cookie("baz", "BAZ")
1115
+ ok {resp.headers['Set-Cookie']} == "foo=FOO\nbar=BAR\nbaz=BAZ"
1116
+ end
690
1117
 
691
- topic 'K8::RESPONSE_CLASS=' do
1118
+ spec "[!7otip] returns cookie string." do
1119
+ resp = K8::RackResponse.new
1120
+ ok {resp.set_cookie("foo", "FOO")} == "foo=FOO"
1121
+ ok {resp.set_cookie("bar", "BAR", path: '/', httponly: true)} == "bar=BAR; Path=/; HttpOnly"
1122
+ end
692
1123
 
693
- spec "[!c1bd0] changes default response class." do
694
- original = K8::RESPONSE_CLASS
695
- begin
696
- K8.RESPONSE_CLASS = Hash
697
- ok {K8::RESPONSE_CLASS} == Hash
698
- ensure
699
- K8.RESPONSE_CLASS = original
1124
+ spec "[!oanme] converts Time object into HTTP timestamp string." do
1125
+ resp = K8::RackResponse.new
1126
+ resp.set_cookie("hom", "HOMHOM", expires: Time.utc(2001, 11, 30, 12, 34, 56))
1127
+ ok {resp.headers['Set-Cookie']} == "hom=HOMHOM; Expires=Fri, 30 Nov 2001 12:34:56 GMT"
700
1128
  end
1129
+
701
1130
  end
702
1131
 
1132
+
703
1133
  end
704
1134
 
705
1135
 
@@ -707,29 +1137,20 @@ Oktest.scope do
707
1137
 
708
1138
  fixture :action do
709
1139
  env = new_env("GET", "/books")
710
- TestBaseAction.new(K8::Request.new(env), K8::Response.new())
1140
+ TestBaseAction.new(K8::RackRequest.new(env), K8::RackResponse.new())
711
1141
  end
712
1142
 
713
1143
 
714
1144
  topic '#initialize()' do
715
1145
 
716
1146
  spec "[!uotpb] accepts request and response objects." do
717
- req = K8::Request.new(new_env("GET", "/books"))
718
- resp = K8::Response.new()
1147
+ req = K8::RackRequest.new(new_env("GET", "/books"))
1148
+ resp = K8::RackResponse.new()
719
1149
  action = K8::BaseAction.new(req, resp)
720
1150
  ok {action.instance_variable_get('@req')}.same?(req)
721
1151
  ok {action.instance_variable_get('@resp')}.same?(resp)
722
1152
  end
723
1153
 
724
- spec "[!7sfyf] sets session object." do
725
- d = {'a'=>1}
726
- req = K8::Request.new(new_env("GET", "/books", env: {'rack.session'=>d}))
727
- resp = K8::Response.new()
728
- action = K8::BaseAction.new(req, resp)
729
- ok {action.instance_variable_get('@sess')}.same?(d)
730
- ok {action.sess}.same?(d)
731
- end
732
-
733
1154
  end
734
1155
 
735
1156
 
@@ -776,7 +1197,7 @@ Oktest.scope do
776
1197
  mapping '/{code}', :GET=>:do_show, :PUT=>:do_update
777
1198
  end
778
1199
  args_list = []
779
- cls._action_method_mapping.each do |*args|
1200
+ cls._mappings.each do |args|
780
1201
  args_list << args
781
1202
  end
782
1203
  ok {args_list} == [
@@ -794,12 +1215,12 @@ Oktest.scope do
794
1215
  infos = BooksAction._build_action_info('/api/books')
795
1216
  #
796
1217
  ok {infos[:do_index]}.is_a?(K8::ActionInfo)
797
- ok {infos[:do_index].method} == :GET
798
- ok {infos[:do_index].urlpath} == '/api/books/'
1218
+ ok {infos[:do_index].meth} == :GET
1219
+ ok {infos[:do_index].path} == '/api/books/'
799
1220
  #
800
1221
  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'
1222
+ ok {infos[:do_update].meth} == :PUT
1223
+ ok {infos[:do_update].path(123)} == '/api/books/123'
803
1224
  end
804
1225
 
805
1226
  end
@@ -812,12 +1233,12 @@ Oktest.scope do
812
1233
  cls = BooksAction
813
1234
  #
814
1235
  ok {cls[:do_create]}.is_a?(K8::ActionInfo)
815
- ok {cls[:do_create].method} == :POST
816
- ok {cls[:do_create].urlpath} == '/api/books/'
1236
+ ok {cls[:do_create].meth} == :POST
1237
+ ok {cls[:do_create].path} == '/api/books/'
817
1238
  #
818
1239
  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'
1240
+ ok {cls[:do_show].meth} == :GET
1241
+ ok {cls[:do_show].path(123)} == '/api/books/123'
821
1242
  end
822
1243
 
823
1244
  spec "[!6g2iw] returns nil when not mounted yet." do
@@ -838,7 +1259,7 @@ Oktest.scope do
838
1259
 
839
1260
  fixture :action_obj do
840
1261
  env = new_env("GET", "/", env: {'rack.session'=>{}})
841
- BooksAction.new(K8::Request.new(env), K8::Response.new())
1262
+ BooksAction.new(K8::RackRequest.new(env), K8::RackResponse.new())
842
1263
  end
843
1264
 
844
1265
 
@@ -873,6 +1294,20 @@ Oktest.scope do
873
1294
  end
874
1295
 
875
1296
 
1297
+ topic '#initialize()' do
1298
+
1299
+ spec "[!7sfyf] sets session object." do
1300
+ d = {'a'=>1}
1301
+ req = K8::RackRequest.new(new_env("GET", "/books", env: {'rack.session'=>d}))
1302
+ resp = K8::RackResponse.new()
1303
+ action = K8::Action.new(req, resp)
1304
+ ok {action.instance_variable_get('@sess')}.same?(d)
1305
+ ok {action.sess}.same?(d)
1306
+ end
1307
+
1308
+ end
1309
+
1310
+
876
1311
  topic '#before_action()' do
877
1312
  end
878
1313
 
@@ -895,7 +1330,7 @@ Oktest.scope do
895
1330
 
896
1331
  spec "[!d5v0l] handles exception when handler method defined." do
897
1332
  env = new_env("POST", "/", env: {'rack.session'=>{}})
898
- action_obj = TestExceptionAction.new(K8::Request.new(env), K8::Response.new())
1333
+ action_obj = TestExceptionAction.new(K8::RackRequest.new(env), K8::RackResponse.new())
899
1334
  result = nil
900
1335
  pr = proc { result = action_obj.handle_action(:do_create, []) }
901
1336
  ok {pr}.raise?(ZeroDivisionError)
@@ -1167,13 +1602,13 @@ Oktest.scope do
1167
1602
 
1168
1603
  fixture :action_obj do
1169
1604
  env = new_env('GET', '/')
1170
- action = K8::Action.new(K8::Request.new(env), K8::Response.new)
1605
+ action = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1171
1606
  end
1172
1607
 
1173
1608
  spec "[!8chgu] returns false when requested with 'XMLHttpRequest'." do
1174
1609
  headers = {'X-Requested-With'=>'XMLHttpRequest'}
1175
1610
  env = new_env('GET', '/', headers: headers)
1176
- action = K8::Action.new(K8::Request.new(env), K8::Response.new)
1611
+ action = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1177
1612
  action.instance_exec(self) do |_|
1178
1613
  _.ok {csrf_protection_required?} == false
1179
1614
  end
@@ -1182,7 +1617,7 @@ Oktest.scope do
1182
1617
  spec "[!vwrqv] returns true when request method is one of POST, PUT, or DELETE." do
1183
1618
  ['POST', 'PUT', 'DELETE'].each do |meth|
1184
1619
  env = new_env(meth, '/')
1185
- action = K8::Action.new(K8::Request.new(env), K8::Response.new)
1620
+ action = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1186
1621
  action.instance_exec(self) do |_|
1187
1622
  _.ok {csrf_protection_required?} == true
1188
1623
  end
@@ -1192,7 +1627,7 @@ Oktest.scope do
1192
1627
  spec "[!jfhla] returns true when request method is GET or HEAD." do
1193
1628
  ['GET', 'HEAD'].each do |meth|
1194
1629
  env = new_env(meth, '/')
1195
- action = K8::Action.new(K8::Request.new(env), K8::Response.new)
1630
+ action = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1196
1631
  action.instance_exec(self) do |_|
1197
1632
  _.ok {csrf_protection_required?} == false
1198
1633
  end
@@ -1208,7 +1643,7 @@ Oktest.scope do
1208
1643
  headers = {'Cookie'=>"_csrf=abc123"}
1209
1644
  form = {"_csrf"=>"abc123"}
1210
1645
  env = new_env('POST', '/', form: form, headers: headers)
1211
- action = K8::Action.new(K8::Request.new(env), K8::Response.new)
1646
+ action = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1212
1647
  action.instance_exec(self) do |_|
1213
1648
  pr = proc { csrf_protection() }
1214
1649
  _.ok {pr}.NOT.raise?
@@ -1219,7 +1654,7 @@ Oktest.scope do
1219
1654
  headers = {'Cookie'=>"_csrf=abc123"}
1220
1655
  form = {"_csrf"=>"abc999"}
1221
1656
  env = new_env('POST', '/', form: form, headers: headers)
1222
- action = K8::Action.new(K8::Request.new(env), K8::Response.new)
1657
+ action = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1223
1658
  action.instance_exec(self) do |_|
1224
1659
  pr = proc { csrf_protection() }
1225
1660
  _.ok {pr}.raise?(K8::HttpException, "invalid csrf token")
@@ -1260,7 +1695,7 @@ Oktest.scope do
1260
1695
 
1261
1696
  spec "[!pal33] returns csrf token in request parameter." do
1262
1697
  env = new_env("POST", "/", form: {"_csrf"=>"foobar999"})
1263
- action_obj = K8::Action.new(K8::Request.new(env), K8::Response.new)
1698
+ action_obj = K8::Action.new(K8::RackRequest.new(env), K8::RackResponse.new)
1264
1699
  action_obj.instance_exec(self) do |_|
1265
1700
  _.ok {csrf_get_param()} == "foobar999"
1266
1701
  end
@@ -1467,36 +1902,36 @@ Oktest.scope do
1467
1902
  spec "[!btt2g] returns ActionInfoN object when number of urlpath parameter <= 4." do
1468
1903
  info = K8::ActionInfo.create('GET', '/books')
1469
1904
  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)\)$/)
1905
+ ok {info.path} == '/books'
1906
+ ok {->{ info.path('a') }}.raise?(ArgumentError, /^wrong number of arguments \((1 for 0|given 1, expected 0)\)$/)
1472
1907
  #
1473
1908
  info = K8::ActionInfo.create('GET', '/books/{id}')
1474
1909
  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)\)$/)
1910
+ ok {info.path('a')} == '/books/a'
1911
+ ok {->{ info.path() }}.raise?(ArgumentError, /^wrong number of arguments \((0 for 1|given 0, expected 1)\)$/)
1477
1912
  #
1478
1913
  info = K8::ActionInfo.create('GET', '/books/{id}/comments/{comment_id}')
1479
1914
  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)\)$/)
1915
+ ok {info.path('a', 'b')} == '/books/a/comments/b'
1916
+ ok {->{info.path('a')}}.raise?(ArgumentError, /^wrong number of arguments \((1 for 2|given 1, expected 2)\)$/)
1482
1917
  #
1483
1918
  info = K8::ActionInfo.create('GET', '/books/{id}/{title}/{code}')
1484
1919
  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)\)$/)
1920
+ ok {info.path('a', 'b', 'c')} == '/books/a/b/c'
1921
+ ok {->{info.path(1,2)}}.raise?(ArgumentError, /^wrong number of arguments \((2 for 3|given 2, expected 3)\)$/)
1487
1922
  #
1488
1923
  info = K8::ActionInfo.create('GET', '/books/{id}/{title}/{code}/{ref}')
1489
1924
  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)\)$/)
1925
+ ok {info.path('a', 'b', 'c', 'd')} == '/books/a/b/c/d'
1926
+ ok {->{info.path}}.raise?(ArgumentError, /^wrong number of arguments \((0 for 4|given 0, expected 4)\)$/)
1492
1927
  end
1493
1928
 
1494
1929
  spec "[!x5yx2] returns ActionInfo object when number of urlpath parameter > 4." do
1495
1930
  info = K8::ActionInfo.create('GET', '/books/{id}/{title}/{code}/{ref}/{x}')
1496
1931
  ok {info}.is_a?(K8::ActionInfo)
1497
- ok {info.urlpath('a', 'b', 'c', 'd', 'e')} == "/books/a/b/c/d/e"
1932
+ ok {info.path('a', 'b', 'c', 'd', 'e')} == "/books/a/b/c/d/e"
1498
1933
  #
1499
- ok {->{info.urlpath('a','b','c')}}.raise?(ArgumentError, "too few arguments")
1934
+ ok {->{info.path('a','b','c')}}.raise?(ArgumentError, "too few arguments")
1500
1935
  end
1501
1936
 
1502
1937
  end
@@ -1520,249 +1955,82 @@ Oktest.scope do
1520
1955
  end
1521
1956
 
1522
1957
 
1523
- topic K8::DefaultPatterns do
1958
+ topic K8::ActionMapping do
1524
1959
 
1525
1960
 
1526
- topic '#register()' do
1961
+ topic '#initialize()' do
1527
1962
 
1528
- spec "[!yfsom] registers urlpath param name, default pattern and converter block." do
1529
- K8::DefaultPatterns.new.instance_exec(self) do |_|
1530
- _.ok {@patterns.length} == 0
1531
- register(/_id\z/, '\d+') {|x| x.to_i }
1532
- _.ok {@patterns.length} == 1
1533
- _.ok {@patterns[0][0]} == /_id\z/
1534
- _.ok {@patterns[0][1]} == '\d+'
1535
- _.ok {@patterns[0][2]}.is_a?(Proc)
1536
- _.ok {@patterns[0][2].call("123")} == 123
1963
+ spec "[!buj0d] prepares LRU cache if cache size specified." do
1964
+ mapping = K8::ActionMapping.new([], urlpath_cache_size: 3)
1965
+ mapping.instance_exec(self) do |_|
1966
+ _.ok {@urlpath_cache_size} == 3
1967
+ _.ok {@urlpath_lru_cache} == {}
1537
1968
  end
1538
- end
1539
-
1540
- end
1541
-
1542
-
1543
- topic '#unregister()' do
1544
-
1545
- spec "[!3gplv] deletes matched record." do
1546
- K8::DefaultPatterns.new.instance_exec(self) do |_|
1547
- register("id", '\d+') {|x| x.to_i }
1548
- register(/_id\z/, '\d+') {|x| x.to_i }
1549
- _.ok {@patterns.length} == 2
1550
- unregister(/_id\z/)
1551
- _.ok {@patterns.length} == 1
1552
- _.ok {@patterns[0][0]} == "id"
1969
+ #
1970
+ mapping = K8::ActionMapping.new([], urlpath_cache_size: 0)
1971
+ mapping.instance_exec(self) do |_|
1972
+ _.ok {@urlpath_cache_size} == 0
1973
+ _.ok {@urlpath_lru_cache} == nil
1553
1974
  end
1554
1975
  end
1555
1976
 
1556
- end
1557
-
1558
-
1559
- topic '#lookup()' do
1560
-
1561
- spec "[!dvbqx] returns default pattern string and converter proc when matched." do
1562
- K8::DefaultPatterns.new.instance_exec(self) do |_|
1563
- register("id", '\d+') {|x| x.to_i }
1564
- register(/_id\z/, '\d+') {|x| x.to_i }
1565
- _.ok {lookup("id")}.is_a?(Array).length(2)
1566
- _.ok {lookup("id")[0]} == '\d+'
1567
- _.ok {lookup("id")[1].call("123")} == 123
1568
- _.ok {lookup("book_id")[0]} == '\d+'
1569
- _.ok {lookup("book_id")[1]}.is_a?(Proc)
1570
- _.ok {lookup("book_id")[1].call("123")} == 123
1977
+ spec "[!wsz8g] compiles urlpath mapping passed." do
1978
+ mapping = K8::ActionMapping.new([
1979
+ ['/api/books', BooksAction],
1980
+ ])
1981
+ ok {mapping.urlpath_rexp} == %r'\A/api/books/\d+(?:(\z)|/edit(\z))\z'
1982
+ mapping.instance_exec(self) do |_|
1983
+ _.ok {@fixed_endpoints.keys} == ['/api/books/', '/api/books/new']
1984
+ _.ok {@variable_endpoints.map{|x| x[0]}} == ['/api/books/{id}', '/api/books/{id}/edit']
1571
1985
  end
1572
1986
  end
1573
1987
 
1574
- spec "[!6hblo] returns '[^/]*?' and nil as default pattern and converter proc when nothing matched." do
1575
- K8::DefaultPatterns.new.instance_exec(self) do |_|
1576
- register("id", '\d+') {|x| x.to_i }
1577
- register(/_id\z/, '\d+') {|x| x.to_i }
1578
- _.ok {lookup("code")}.is_a?(Array).length(2)
1579
- _.ok {lookup("code")[0]} == '[^/]+?'
1580
- _.ok {lookup("code")[1]} == nil
1988
+ spec "[!34o67] keyword arg 'enable_urlpath_param_range' controls to generate range object or not." do
1989
+ arr = [['/books', BooksAction]]
1990
+ #
1991
+ mapping1 = K8::ActionMapping.new(arr, enable_urlpath_param_range: true)
1992
+ mapping1.instance_exec(self) do |_|
1993
+ tuple = @variable_endpoints.find {|a| a[0] == '/books/{id}' }
1994
+ _.ok {tuple[-1]} == (7..-1)
1995
+ end
1996
+ #
1997
+ mapping2 = K8::ActionMapping.new(arr, enable_urlpath_param_range: false)
1998
+ mapping2.instance_exec(self) do |_|
1999
+ tuple = @variable_endpoints.find {|a| a[0] == '/books/{id}' }
2000
+ _.ok {tuple[-1]} == nil
1581
2001
  end
1582
2002
  end
1583
2003
 
1584
2004
  end
1585
2005
 
1586
- end
1587
2006
 
2007
+ topic '#build()' do
1588
2008
 
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
-
1625
- topic K8::ActionMethodMapping do
1626
-
1627
- fixture :mapping do
1628
- mapping = K8::ActionMethodMapping.new
1629
- mapping.map '/', :GET=>:do_index, :POST=>:do_create
1630
- mapping.map '/{id:\d+}', :GET=>:do_show, :PUT=>:do_update
1631
- mapping
1632
- end
1633
-
1634
- fixture :methods1 do
1635
- {:GET=>:do_index, :POST=>:do_create}
1636
- end
1637
-
1638
- fixture :methods2 do
1639
- {:GET=>:do_show, :PUT=>:do_update}
1640
- end
1641
-
1642
-
1643
- topic '#map()' do
1644
-
1645
- spec "[!s7cs9] maps urlpath and methods." do
1646
- |mapping|
1647
- arr = mapping.instance_variable_get('@mappings')
1648
- ok {arr}.is_a?(Array)
1649
- ok {arr.length} == 2
1650
- ok {arr[0]} == ['/', {:GET=>:do_index, :POST=>:do_create}]
1651
- ok {arr[1]} == ['/{id:\d+}', {:GET=>:do_show, :PUT=>:do_update}]
1652
- end
1653
-
1654
- spec "[!o6cxr] returns self." do
1655
- |mapping|
1656
- ok {mapping.map '/new', :GET=>:do_new}.same?(mapping)
1657
- end
1658
-
1659
- end
1660
-
1661
-
1662
- topic '#each()' do
1663
-
1664
- spec "[!62y5q] yields each urlpath pattern and action methods." do
1665
- |mapping, methods1, methods2|
1666
- arr = []
1667
- mapping.each do |urlpath_pat, action_methods|
1668
- arr << [urlpath_pat, action_methods]
1669
- end
1670
- ok {arr} == [
1671
- ['/', methods1],
1672
- ['/{id:\d+}', methods2],
1673
- ]
1674
- end
1675
-
1676
- end
1677
-
1678
-
1679
- end
1680
-
1681
-
1682
- topic K8::ActionMapping do
1683
-
1684
-
1685
- topic '#initialize()' do
1686
-
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} == {}
1692
- 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
1698
- end
1699
- end
1700
-
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
1710
- end
1711
-
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
1726
- end
1727
-
1728
- end
1729
-
1730
-
1731
- topic '#compile()' do
1732
-
1733
- fixture :proc1 do
1734
- proc {|x| x.to_i }
1735
- end
1736
-
1737
- fixture :proc2 do
1738
- proc {|x| x.to_i }
2009
+ fixture :proc_int do
2010
+ K8::ActionMapping::URLPATH_PARAM_TYPES[0][3]
1739
2011
  end
1740
2012
 
1741
2013
  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
2014
  K8::ActionMapping.new([
1747
2015
  ['/api', [
1748
2016
  ['/books', BooksAction],
1749
2017
  ['/books/{book_id}', BookCommentsAction],
1750
2018
  ]],
1751
- ], default_patterns: dp)
2019
+ ])
1752
2020
  end
1753
2021
 
1754
2022
  spec "[!6f3vl] compiles urlpath mapping." do
1755
2023
  |mapping|
1756
- mapping.instance_exec(self) do |_|
1757
- _.ok {@urlpath_rexp}.is_a?(Regexp)
1758
- _.ok {@urlpath_rexp} == Regexp.compile('
2024
+ ok {mapping.urlpath_rexp}.is_a?(Regexp)
2025
+ ok {mapping.urlpath_rexp} == Regexp.compile('
1759
2026
  \A/api
1760
2027
  (?: /books
1761
- (?: /\d+(\z) | /\d+/edit(\z) )
2028
+ /\d+ (?: (\z) | /edit(\z) )
1762
2029
  | /books/\d+
1763
2030
  (?: /comments(\z) | /comments/\d+(\z) )
1764
2031
  )
1765
2032
  \z'.gsub(/\s/, ''))
2033
+ mapping.instance_exec(self) do |_|
1766
2034
  _.ok {@fixed_endpoints.keys} == ["/api/books/", "/api/books/new"]
1767
2035
  _.ok {@variable_endpoints.map{|x| x[0..2] }} == [
1768
2036
  ["/api/books/{id}", BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}],
@@ -1774,76 +2042,76 @@ Oktest.scope do
1774
2042
  end
1775
2043
 
1776
2044
  spec "[!w45ad] can compile nested array." do
1777
- |mapping, proc1, proc2|
1778
- mapping.instance_exec(self) do |_|
1779
- _.ok {@urlpath_rexp} == Regexp.compile('
2045
+ |mapping, proc_int|
2046
+ ok {mapping.urlpath_rexp} == Regexp.compile('
1780
2047
  \A /api
1781
2048
  (?: /books
1782
- (?: /\d+(\z) | /\d+/edit(\z) )
2049
+ /\d+ (?: (\z) | /edit(\z) )
1783
2050
  | /books/\d+
1784
2051
  (?: /comments(\z) | /comments/\d+(\z) )
1785
2052
  )
1786
2053
  \z'.gsub(/\s/, ''))
2054
+ mapping.instance_exec(self) do |_|
1787
2055
  _.ok {@variable_endpoints} == [
1788
2056
  ["/api/books/{id}",
1789
2057
  BooksAction,
1790
2058
  {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
1791
2059
  /\A\/api\/books\/(\d+)\z/,
1792
- ["id"], [proc1], (11..-1),
2060
+ ["id"], [proc_int], (11..-1),
1793
2061
  ],
1794
2062
  ["/api/books/{id}/edit",
1795
2063
  BooksAction,
1796
2064
  {:GET=>:do_edit},
1797
2065
  /\A\/api\/books\/(\d+)\/edit\z/,
1798
- ["id"], [proc1], (11..-6),
2066
+ ["id"], [proc_int], (11..-6),
1799
2067
  ],
1800
2068
  ["/api/books/{book_id}/comments",
1801
2069
  BookCommentsAction,
1802
2070
  {:GET=>:do_comments},
1803
2071
  /\A\/api\/books\/(\d+)\/comments\z/,
1804
- ["book_id"], [proc2], (11..-10),
2072
+ ["book_id"], [proc_int], (11..-10),
1805
2073
  ],
1806
2074
  ["/api/books/{book_id}/comments/{comment_id}",
1807
2075
  BookCommentsAction,
1808
2076
  {:GET=>:do_comment},
1809
2077
  /\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
1810
- ["book_id", "comment_id"], [proc2, proc2], nil,
2078
+ ["book_id", "comment_id"], [proc_int, proc_int], nil,
1811
2079
  ],
1812
2080
  ]
1813
2081
  _.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}],
2082
+ "/api/books/" =>["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}, []],
2083
+ "/api/books/new"=>["/api/books/new", BooksAction, {:GET=>:do_new}, []],
1816
2084
  }
1817
2085
  end
1818
2086
  end
1819
2087
 
1820
2088
  spec "[!z2iax] classifies urlpath contains any parameter as variable one." do
1821
- |mapping, proc1, proc2|
2089
+ |mapping, proc_int|
1822
2090
  mapping.instance_exec(self) do |_|
1823
2091
  _.ok {@variable_endpoints} == [
1824
2092
  ["/api/books/{id}",
1825
2093
  BooksAction,
1826
2094
  {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
1827
2095
  /\A\/api\/books\/(\d+)\z/,
1828
- ["id"], [proc1], (11..-1),
2096
+ ["id"], [proc_int], (11..-1),
1829
2097
  ],
1830
2098
  ["/api/books/{id}/edit",
1831
2099
  BooksAction,
1832
2100
  {:GET=>:do_edit},
1833
2101
  /\A\/api\/books\/(\d+)\/edit\z/,
1834
- ["id"], [proc1], (11..-6),
2102
+ ["id"], [proc_int], (11..-6),
1835
2103
  ],
1836
2104
  ["/api/books/{book_id}/comments",
1837
2105
  BookCommentsAction,
1838
2106
  {:GET=>:do_comments},
1839
2107
  /\A\/api\/books\/(\d+)\/comments\z/,
1840
- ["book_id"], [proc2], (11..-10),
2108
+ ["book_id"], [proc_int], (11..-10),
1841
2109
  ],
1842
2110
  ["/api/books/{book_id}/comments/{comment_id}",
1843
2111
  BookCommentsAction,
1844
2112
  {:GET=>:do_comment},
1845
2113
  /\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
1846
- ["book_id", "comment_id"], [proc2, proc2], nil,
2114
+ ["book_id", "comment_id"], [proc_int, proc_int], nil,
1847
2115
  ],
1848
2116
  ]
1849
2117
  end
@@ -1853,8 +2121,8 @@ Oktest.scope do
1853
2121
  |mapping|
1854
2122
  mapping.instance_exec(self) do |_|
1855
2123
  _.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}],
2124
+ "/api/books/" => ["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}, []],
2125
+ "/api/books/new" => ["/api/books/new", BooksAction, {:GET=>:do_new}, []],
1858
2126
  }
1859
2127
  end
1860
2128
  end
@@ -1878,56 +2146,55 @@ Oktest.scope do
1878
2146
  ])
1879
2147
  #
1880
2148
  ok {Ex_6xwhq[:do_create]} != nil
1881
- ok {Ex_6xwhq[:do_create].method} == :POST
1882
- ok {Ex_6xwhq[:do_create].urlpath} == '/test/example4'
2149
+ ok {Ex_6xwhq[:do_create].meth} == :POST
2150
+ ok {Ex_6xwhq[:do_create].path} == '/test/example4'
1883
2151
  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'
2152
+ ok {Ex_6xwhq[:do_update].meth} == :PUT
2153
+ ok {Ex_6xwhq[:do_update].path(123)} == '/test/example4/123'
1886
2154
  end
1887
2155
 
1888
2156
  spec "[!wd2eb] accepts subclass of Action class." do
1889
- _, proc1 = K8::DEFAULT_PATTERNS.lookup('id')
1890
- _, proc2 = K8::DEFAULT_PATTERNS.lookup('book_id')
2157
+ proc_int = K8::ActionMapping::URLPATH_PARAM_TYPES[0][3]
1891
2158
  mapping = K8::ActionMapping.new([
1892
2159
  ['/api/books', BooksAction],
1893
2160
  ['/api/books/{book_id}', BookCommentsAction],
1894
2161
  ])
1895
- mapping.instance_exec(self) do |_|
1896
- _.ok {@urlpath_rexp} == Regexp.compile('
2162
+ ok {mapping.urlpath_rexp} == Regexp.compile('
1897
2163
  \A (?: /api/books
1898
- (?: /\d+(\z) | /\d+/edit(\z) )
2164
+ /\d+ (?: (\z) | /edit(\z) )
1899
2165
  | /api/books/\d+
1900
2166
  (?: /comments(\z) | /comments/\d+(\z) )
1901
2167
  )
1902
2168
  \z'.gsub(/\s/, ''))
2169
+ mapping.instance_exec(self) do |_|
1903
2170
  _.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}],
2171
+ "/api/books/" =>["/api/books/", BooksAction, {:GET=>:do_index, :POST=>:do_create}, []],
2172
+ "/api/books/new"=>["/api/books/new", BooksAction, {:GET=>:do_new}, []],
1906
2173
  }
1907
2174
  _.ok {@variable_endpoints} == [
1908
2175
  ["/api/books/{id}",
1909
2176
  BooksAction,
1910
2177
  {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
1911
2178
  /\A\/api\/books\/(\d+)\z/,
1912
- ["id"], [proc1], (11..-1),
2179
+ ["id"], [proc_int], (11..-1),
1913
2180
  ],
1914
2181
  ["/api/books/{id}/edit",
1915
2182
  BooksAction,
1916
2183
  {:GET=>:do_edit},
1917
2184
  /\A\/api\/books\/(\d+)\/edit\z/,
1918
- ["id"], [proc1], (11..-6),
2185
+ ["id"], [proc_int], (11..-6),
1919
2186
  ],
1920
2187
  ["/api/books/{book_id}/comments",
1921
2188
  BookCommentsAction,
1922
2189
  {:GET=>:do_comments},
1923
2190
  /\A\/api\/books\/(\d+)\/comments\z/,
1924
- ["book_id"], [proc2], (11..-10),
2191
+ ["book_id"], [proc_int], (11..-10),
1925
2192
  ],
1926
2193
  ["/api/books/{book_id}/comments/{comment_id}",
1927
2194
  BookCommentsAction,
1928
2195
  {:GET=>:do_comment},
1929
2196
  /\A\/api\/books\/(\d+)\/comments\/(\d+)\z/,
1930
- ["book_id", "comment_id"], [proc2, proc2], nil,
2197
+ ["book_id", "comment_id"], [proc_int, proc_int], nil,
1931
2198
  ],
1932
2199
  ]
1933
2200
  end
@@ -1962,17 +2229,16 @@ Oktest.scope do
1962
2229
  File.open(filename, 'w') {|f| f << content }
1963
2230
  at_end { File.unlink filename; Dir.rmdir dirname }
1964
2231
  #
1965
- _, proc1 = K8::DEFAULT_PATTERNS.lookup('id')
1966
- _, proc2 = K8::DEFAULT_PATTERNS.lookup('book_id')
2232
+ proc_int = K8::ActionMapping::URLPATH_PARAM_TYPES[0][3]
1967
2233
  mapping = K8::ActionMapping.new([
1968
2234
  ['/api/example', './test_l2kz5/sample:Ex_l2kz5::Example_l2kz5'],
1969
2235
  ])
1970
2236
  mapping.instance_exec(self) do |_|
1971
2237
  _.ok {@fixed_endpoints} == {
1972
- "/api/example"=>["/api/example", Ex_l2kz5::Example_l2kz5, {:GET=>:do_index}],
2238
+ "/api/example"=>["/api/example", Ex_l2kz5::Example_l2kz5, {:GET=>:do_index}, []],
1973
2239
  }
1974
2240
  _.ok {@variable_endpoints} == [
1975
- ["/api/example/{id}", Ex_l2kz5::Example_l2kz5, {:GET=>:do_show}, /\A\/api\/example\/(\d+)\z/, ["id"], [proc1], (13..-1)],
2241
+ ["/api/example/{id}", Ex_l2kz5::Example_l2kz5, {:GET=>:do_show}, /\A\/api\/example\/(\d+)\z/, ["id"], [proc_int], (13..-1)],
1976
2242
  ]
1977
2243
  end
1978
2244
  end
@@ -2000,17 +2266,17 @@ Oktest.scope do
2000
2266
  ['/books/{book_id}', BookCommentsAction],
2001
2267
  ]],
2002
2268
  ])
2003
- mapping.instance_exec(self) do |_|
2004
- _.ok {@urlpath_rexp} == Regexp.compile('
2269
+ ok {mapping.urlpath_rexp} == Regexp.compile('
2005
2270
  \A /api
2006
2271
  (?: /books
2007
- (?:/\d+(\z)|/\d+/edit(\z))
2272
+ /\d+(?:(\z)|/edit(\z))
2008
2273
  | /books/\d+
2009
2274
  (?:/comments(\z)|/comments/\d+(\z))
2010
2275
  )
2011
2276
  \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}]
2277
+ mapping.instance_exec(self) do |_|
2278
+ _.ok {@fixed_endpoints['/api/samples/']} == ["/api/samples/", klass, {:GET=>:do_index}, []]
2279
+ _.ok {@fixed_endpoints['/api/samples/new']} == ["/api/samples/new", klass, {:GET=>:do_new}, []]
2014
2280
  end
2015
2281
  end
2016
2282
 
@@ -2024,90 +2290,124 @@ Oktest.scope do
2024
2290
  ['/test', klass],
2025
2291
  ]],
2026
2292
  ])
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'
2293
+ ok {mapping.urlpath_rexp} == %r'\A/api/test/\d+(\z)\z'
2294
+ end
2295
+
2296
+ spec "[!abj34] ex: (?:/\d+(\z)|/\d+/edit(\z)) -> /d+(?:(\z)|/edit(\z))" do
2297
+ klass = Class.new(K8::Action) do
2298
+ mapping '/' , :GET=>:do_index
2299
+ mapping '/{id}' , :GET=>:do_show
2300
+ mapping '/{id}/edit' , :GET=>:do_edit
2301
+ mapping '/{id}/comments', :GET=>:do_comments
2302
+ def do_index; end
2303
+ def do_show(id); end
2304
+ def do_edit(id); end
2305
+ def do_comments(id); end
2030
2306
  end
2307
+ mapping = K8::ActionMapping.new([
2308
+ ['/api', [
2309
+ ['/books', BooksAction],
2310
+ ['/samples', klass],
2311
+ ]],
2312
+ ])
2313
+ ok {mapping.urlpath_rexp} == Regexp.compile('
2314
+ \A /api
2315
+ (?: /books
2316
+ /\d+(?:(\z)|/edit(\z))
2317
+ | /samples
2318
+ /\d+(?:(\z)|/edit(\z)|/comments(\z))
2319
+ )
2320
+ \z'.gsub(/\s+/, ''))
2031
2321
  end
2032
2322
 
2033
- end
2323
+ spec "[!m51yy] regards '.*' at end of urlpath pattern as extension." do
2324
+ cls = Class.new(K8::Action) do
2325
+ mapping '.*', :GET=>:do_index
2326
+ mapping '/{id}.*', :GET=>:do_show
2327
+ def do_index; end
2328
+ def do_show(id); end
2329
+ end
2330
+ am = K8::ActionMapping.new([
2331
+ ['/api', [
2332
+ ['/foo', cls],
2333
+ ]],
2334
+ ])
2335
+ ok {am.urlpath_rexp} == Regexp.compile('
2336
+ \A /api
2337
+ /foo (?: (?:\.\w+)? (\z)
2338
+ | /\d+ (?:\.\w+)? (\z)
2339
+ )
2340
+ \z'.gsub(/\s+/, ''))
2341
+ end
2034
2342
 
2343
+ end
2035
2344
 
2036
- topic '#lookup()' do
2037
2345
 
2038
- fixture :proc1 do
2039
- proc {|x| x.to_i }
2040
- end
2346
+ topic '#find()' do
2041
2347
 
2042
2348
  fixture :mapping do
2043
- |proc1|
2044
- dp = K8::DefaultPatterns.new
2045
- dp.register('id', '\d+', &proc1)
2046
- dp.register(/_id$/, '\d+', &proc1)
2047
2349
  K8::ActionMapping.new([
2048
2350
  ['/api', [
2049
2351
  ['/books', BooksAction],
2050
2352
  ['/books/{book_id}', BookCommentsAction],
2051
2353
  ]],
2052
- ], default_patterns: dp, urlpath_cache_size: 3)
2354
+ ], urlpath_cache_size: 3)
2053
2355
  end
2054
2356
 
2055
- spec "[!jyxlm] returns action class and methods, parameter names and values." do
2357
+ spec "[!jyxlm] returns action class, action methods and urlpath param args." do
2056
2358
  |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]]
2359
+ tuple = mapping.find('/api/books/123')
2360
+ ok {tuple} == [BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}, [123]]
2361
+ tuple = mapping.find('/api/books/123/comments/999')
2362
+ ok {tuple} == [BookCommentsAction, {:GET=>:do_comment}, [123, 999]]
2061
2363
  end
2062
2364
 
2063
2365
  spec "[!j34yh] finds from fixed urlpaths at first." do
2064
2366
  |mapping|
2065
2367
  mapping.instance_exec(self) do |_|
2066
- _.ok {lookup('/books')} == nil
2368
+ _.ok {find('/books')} == nil
2067
2369
  tuple = @fixed_endpoints['/api/books/']
2068
2370
  _.ok {tuple} != nil
2069
2371
  @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
2372
+ expected = [BooksAction, {:GET=>:do_index, :POST=>:do_create}, []]
2373
+ _.ok {find('/books')} != nil
2374
+ _.ok {find('/books')} == expected
2375
+ _.ok {find('/api/books/')} == expected
2074
2376
  end
2075
2377
  end
2076
2378
 
2077
2379
  spec "[!95q61] finds from variable urlpath patterns when not found in fixed ones." do
2078
2380
  |mapping|
2079
- ok {mapping.lookup('/api/books/123')} == \
2381
+ ok {mapping.find('/api/books/123')} == \
2080
2382
  [
2081
2383
  BooksAction,
2082
2384
  {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete},
2083
- ["id"],
2084
2385
  [123],
2085
2386
  ]
2086
- ok {mapping.lookup('/api/books/123/comments/999')} == \
2387
+ ok {mapping.find('/api/books/123/comments/999')} == \
2087
2388
  [
2088
2389
  BookCommentsAction,
2089
2390
  {:GET=>:do_comment},
2090
- ["book_id", "comment_id"],
2091
2391
  [123, 999],
2092
2392
  ]
2093
2393
  end
2094
2394
 
2095
2395
  spec "[!sos5i] returns nil when request path not matched to urlpath patterns." do
2096
2396
  |mapping|
2097
- ok {mapping.lookup('/api/booking')} == nil
2397
+ ok {mapping.find('/api/booking')} == nil
2098
2398
  end
2099
2399
 
2100
2400
  spec "[!1k1k5] converts urlpath param values by converter procs." do
2101
2401
  |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]]
2402
+ tuple = mapping.find('/api/books/123')
2403
+ ok {tuple[-1]} == [123] # id
2404
+ tuple = mapping.find('/api/books/123/comments/999')
2405
+ ok {tuple[-1]} == [123, 999] # book_id, comment_id
2106
2406
  end
2107
2407
 
2108
2408
  spec "[!uqwr7] stores result into cache if cache is enabled." do
2109
2409
  |mapping|
2110
- tuple = mapping.lookup('/api/books/111')
2410
+ tuple = mapping.find('/api/books/111')
2111
2411
  mapping.instance_exec(self) do |_|
2112
2412
  _.ok {@urlpath_lru_cache} == {'/api/books/111' => tuple}
2113
2413
  end
@@ -2115,11 +2415,11 @@ Oktest.scope do
2115
2415
 
2116
2416
  spec "[!3ps5g] deletes item from cache when cache size over limit." do
2117
2417
  |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')
2418
+ mapping.find('/api/books/1')
2419
+ mapping.find('/api/books/2')
2420
+ mapping.find('/api/books/3')
2421
+ mapping.find('/api/books/4')
2422
+ mapping.find('/api/books/5')
2123
2423
  mapping.instance_exec(self) do |_|
2124
2424
  _.ok {@urlpath_lru_cache.length} == 3
2125
2425
  end
@@ -2128,18 +2428,18 @@ Oktest.scope do
2128
2428
  spec "[!uqwr7] uses LRU as cache algorithm." do
2129
2429
  |mapping|
2130
2430
  mapping.instance_exec(self) do |_|
2131
- t1 = lookup('/api/books/1')
2132
- t2 = lookup('/api/books/2')
2133
- t3 = lookup('/api/books/3')
2431
+ t1 = find('/api/books/1')
2432
+ t2 = find('/api/books/2')
2433
+ t3 = find('/api/books/3')
2134
2434
  _.ok {@urlpath_lru_cache.values} == [t1, t2, t3]
2135
- t4 = lookup('/api/books/4')
2435
+ t4 = find('/api/books/4')
2136
2436
  _.ok {@urlpath_lru_cache.values} == [t2, t3, t4]
2137
- t5 = lookup('/api/books/5')
2437
+ t5 = find('/api/books/5')
2138
2438
  _.ok {@urlpath_lru_cache.values} == [t3, t4, t5]
2139
2439
  #
2140
- lookup('/api/books/4')
2440
+ find('/api/books/4')
2141
2441
  _.ok {@urlpath_lru_cache.values} == [t3, t5, t4]
2142
- lookup('/api/books/3')
2442
+ find('/api/books/3')
2143
2443
  _.ok {@urlpath_lru_cache.values} == [t5, t4, t3]
2144
2444
  end
2145
2445
  end
@@ -2147,103 +2447,148 @@ Oktest.scope do
2147
2447
  end
2148
2448
 
2149
2449
 
2150
- topic '#_compile_urlpath_pat()' do
2450
+ topic '#compile_urlpath()' do
2451
+
2452
+ fixture :proc_int do
2453
+ K8::ActionMapping::URLPATH_PARAM_TYPES[0][3] # for 'int' type
2454
+ end
2151
2455
 
2152
- fixture :proc1 do
2153
- proc {|x| x.to_i }
2456
+ fixture :proc_date do
2457
+ K8::ActionMapping::URLPATH_PARAM_TYPES[1][3] # for 'date' type
2154
2458
  end
2155
2459
 
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
2460
+ fixture :proc_str do
2461
+ K8::ActionMapping::URLPATH_PARAM_TYPES[2][3] # for 'str' type
2162
2462
  end
2163
2463
 
2164
2464
  spec "[!awfgs] returns regexp string, param names, and converter procs." do
2165
- |default_patterns, proc1|
2166
- mapping = K8::ActionMapping.new([], default_patterns: default_patterns)
2465
+ |proc_int|
2466
+ mapping = K8::ActionMapping.new([])
2167
2467
  mapping.instance_exec(self) do |_|
2168
2468
  #
2169
- actual = _compile_urlpath_pat('/books/{id}')
2170
- _.ok {actual} == ['/books/\d+', ['id'], [proc1]]
2469
+ actual = compile_urlpath('/books/{id}')
2470
+ _.ok {actual} == ['/books/\d+', ['id'], [proc_int]]
2171
2471
  #
2172
- actual = _compile_urlpath_pat('/books/{book_id}/comments/{comment_id}')
2173
- _.ok {actual} == ['/books/\d+/comments/\d+', ['book_id', 'comment_id'], [proc1, proc1]]
2472
+ actual = compile_urlpath('/books/{book_id}/comments/{comment_id}')
2473
+ _.ok {actual} == ['/books/\d+/comments/\d+', ['book_id', 'comment_id'], [proc_int, proc_int]]
2174
2474
  #
2175
- actual = _compile_urlpath_pat('/books/{id:[0-9]+}')
2176
- _.ok {actual} == ['/books/[0-9]+', ['id'], [nil]]
2475
+ actual = compile_urlpath('/books/{id:<[0-9]+>}')
2476
+ _.ok {actual} == ['/books/[0-9]+', ['id'], [proc_int]]
2177
2477
  end
2178
2478
  end
2179
2479
 
2180
2480
  spec "[!bi7gr] captures urlpath params when 2nd argument is truthy." do
2181
- |default_patterns, proc1|
2182
- mapping = K8::ActionMapping.new([], default_patterns: default_patterns)
2481
+ |proc_int|
2482
+ mapping = K8::ActionMapping.new([])
2183
2483
  mapping.instance_exec(self) do |_|
2184
- actual = _compile_urlpath_pat('/books/{id}', true)
2185
- _.ok {actual} == ['/books/(\d+)', ['id'], [proc1]]
2484
+ actual = compile_urlpath('/books/{id}', true)
2485
+ _.ok {actual} == ['/books/(\d+)', ['id'], [proc_int]]
2186
2486
  #
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]]
2487
+ actual = compile_urlpath('/books/{book_id}/comments/{comment_id}', true)
2488
+ _.ok {actual} == ['/books/(\d+)/comments/(\d+)', ['book_id', 'comment_id'], [proc_int, proc_int]]
2189
2489
  #
2190
- actual = _compile_urlpath_pat('/books/{id:[0-9]+}', true)
2191
- _.ok {actual} == ['/books/([0-9]+)', ['id'], [nil]]
2490
+ actual = compile_urlpath('/books/{id:<[0-9]+>}', true)
2491
+ _.ok {actual} == ['/books/([0-9]+)', ['id'], [proc_int]]
2192
2492
  end
2193
2493
  end
2194
2494
 
2195
2495
  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)
2496
+ mapping = K8::ActionMapping.new([])
2198
2497
  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)', [], []]
2498
+ _.ok {compile_urlpath('/item/{key:<x|y>}', true)} == ['/item/(x|y)', ['key'], [nil]]
2499
+ _.ok {compile_urlpath('/item/{key:<x|y>}', false)} == ['/item/(?:x|y)', ['key'], [nil]]
2500
+ _.ok {compile_urlpath('/item/{:<x|y>}', true)} == ['/item/(?:x|y)', [], []]
2501
+ _.ok {compile_urlpath('/item/{:<x|y>}', false)} == ['/item/(?:x|y)', [], []]
2203
2502
  end
2204
2503
  end
2205
2504
 
2206
2505
  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)
2506
+ mapping = K8::ActionMapping.new([])
2209
2507
  mapping.instance_exec(self) do |_|
2210
- actual = _compile_urlpath_pat('/books/new')
2508
+ actual = compile_urlpath('/books/new')
2211
2509
  _.ok {actual} == ['/books/new', nil, nil]
2212
2510
  end
2213
2511
  end
2214
2512
 
2513
+ spec "[!9ofdd] supports urlpath param type, for example '{id:int}'." do
2514
+ |proc_int, proc_date, proc_str|
2515
+ mapping = K8::ActionMapping.new([])
2516
+ mapping.instance_exec(self) do |_|
2517
+ actual = compile_urlpath('/books/{id:int}')
2518
+ _.ok {actual} == ['/books/\d+', ['id'], [proc_int]]
2519
+ actual = compile_urlpath('/books/{book_id:int}')
2520
+ _.ok {actual} == ['/books/\d+', ['book_id'], [proc_int]]
2521
+ actual = compile_urlpath('/books/{code:int}')
2522
+ _.ok {actual} == ['/books/\d+', ['code'], [proc_int]]
2523
+ #
2524
+ actual = compile_urlpath('/diary/{today:date}')
2525
+ _.ok {actual} == ['/diary/\d\d\d\d-\d\d-\d\d', ['today'], [proc_date]]
2526
+ #
2527
+ actual = compile_urlpath('/books/{id:str}')
2528
+ _.ok {actual} == ['/books/[^/]+', ['id'], [proc_str]]
2529
+ end
2530
+ end
2531
+
2215
2532
  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')
2533
+ |proc_int|
2534
+ K8::ActionMapping.new([]).instance_exec(self) do |_|
2535
+ actual = compile_urlpath('/api/{:<\d+>}/books')
2219
2536
  _.ok {actual} == ['/api/\d+/books', [], []]
2220
- actual = _compile_urlpath_pat('/api/{:\d+}/books/{id}')
2221
- _.ok {actual} == ['/api/\d+/books/\d+', ['id'], [proc1]]
2537
+ actual = compile_urlpath('/api/{:<\d+>}/books/{id}')
2538
+ _.ok {actual} == ['/api/\d+/books/\d+', ['id'], [proc_int]]
2222
2539
  end
2223
2540
  end
2224
2541
 
2225
2542
  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')
2543
+ |proc_int|
2544
+ K8::ActionMapping.new([]).instance_exec(self) do |_|
2545
+ actual = compile_urlpath('/api/{_ver:<\d+>}/books')
2229
2546
  _.ok {actual} == ['/api/\d+/books', [], []]
2230
- actual = _compile_urlpath_pat('/api/{_ver:\d+}/books/{id}')
2231
- _.ok {actual} == ['/api/\d+/books/\d+', ['id'], [proc1]]
2547
+ actual = compile_urlpath('/api/{_ver:<\d+>}/books/{id}')
2548
+ _.ok {actual} == ['/api/\d+/books/\d+', ['id'], [proc_int]]
2232
2549
  end
2233
2550
  end
2234
2551
 
2235
2552
  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]]
2553
+ |proc_date|
2554
+ K8::ActionMapping.new([]).instance_exec(self) do |_|
2555
+ actual = compile_urlpath('/blog/{date:<\d{4}-\d{2}-\d{2}>}')
2556
+ _.ok {actual} == ['/blog/\d{4}-\d{2}-\d{2}', ['date'], [proc_date]]
2557
+ end
2558
+ end
2559
+
2560
+ spec "[!do1zi] param type is optional (ex: '{id}' or '{id:<\d+>}')." do
2561
+ |proc_int|
2562
+ K8::ActionMapping.new([]).instance_exec(self) do |_|
2563
+ actual = compile_urlpath('/books/{book_id}')
2564
+ _.ok {actual} == ['/books/\d+', ['book_id'], [proc_int]]
2565
+ actual = compile_urlpath('/books/{xxx:<\d\d\d>}')
2566
+ _.ok {actual} == ['/books/\d\d\d', ['xxx'], [nil]]
2567
+ end
2568
+ end
2569
+
2570
+ spec "[!my6as] param pattern is optional (ex: '{id}' or '{id:int}')." do
2571
+ |proc_int|
2572
+ K8::ActionMapping.new([]).instance_exec(self) do |_|
2573
+ actual = compile_urlpath('/books/{book_id}')
2574
+ _.ok {actual} == ['/books/\d+', ['book_id'], [proc_int]]
2575
+ actual = compile_urlpath('/books/{xxx:int}')
2576
+ _.ok {actual} == ['/books/\d+', ['xxx'], [proc_int]]
2577
+ end
2578
+ end
2579
+
2580
+ spec "[!3diea] '{id:<\d+>}' is ok but '{id<\d+>}' raises error." do
2581
+ |proc_int|
2582
+ K8::ActionMapping.new([]).instance_exec(self) do |_|
2583
+ pr = proc { compile_urlpath('/books/{book_id<\d+>}') }
2584
+ _.ok {pr}.raise?(K8::ActionMappingError)
2240
2585
  end
2241
2586
  end
2242
2587
 
2243
2588
  end
2244
2589
 
2245
2590
 
2246
- topic '#_require_action_class()' do
2591
+ topic '#require_action_class()' do
2247
2592
 
2248
2593
  spec "[!px9jy] requires file and finds class object." do
2249
2594
  filename = 'test_px9jy.rb'
@@ -2251,7 +2596,7 @@ Oktest.scope do
2251
2596
  File.open(filename, 'w') {|f| f << content }
2252
2597
  at_end { File.unlink filename }
2253
2598
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2254
- _.ok { _require_action_class './test_px9jy:Ex_px9jy' } == Ex_px9jy
2599
+ _.ok { require_action_class './test_px9jy:Ex_px9jy' } == Ex_px9jy
2255
2600
  end
2256
2601
  end
2257
2602
 
@@ -2261,7 +2606,7 @@ Oktest.scope do
2261
2606
  File.open(filename, 'w') {|f| f << content }
2262
2607
  at_end { File.unlink filename }
2263
2608
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2264
- pr = proc { _require_action_class './test_dlcks:Ex_dlcks' }
2609
+ pr = proc { require_action_class './test_dlcks:Ex_dlcks' }
2265
2610
  _.ok {pr}.raise?(LoadError, "cannot load such file -- homhomhom")
2266
2611
  _.ok {pr.exception.path} == "homhomhom"
2267
2612
  end
@@ -2270,7 +2615,7 @@ Oktest.scope do
2270
2615
  spec "[!mngjz] raises error when failed to load file." do
2271
2616
  filename = 'test_mngjz.rb'
2272
2617
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2273
- pr = proc { _require_action_class './test_mngjz:Ex_mngjz' }
2618
+ pr = proc { require_action_class './test_mngjz:Ex_mngjz' }
2274
2619
  _.ok {pr}.raise?(LoadError, "'./test_mngjz:Ex_mngjz': cannot load './test_mngjz'.")
2275
2620
  end
2276
2621
  end
@@ -2281,7 +2626,7 @@ Oktest.scope do
2281
2626
  File.open(filename, 'w') {|f| f << content }
2282
2627
  at_end { File.unlink filename }
2283
2628
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2284
- _.ok { _require_action_class './test_8n6pf:Ex_8n6pf::Sample' } == Ex_8n6pf::Sample
2629
+ _.ok { require_action_class './test_8n6pf:Ex_8n6pf::Sample' } == Ex_8n6pf::Sample
2285
2630
  end
2286
2631
  end
2287
2632
 
@@ -2291,7 +2636,7 @@ Oktest.scope do
2291
2636
  File.open(filename, 'w') {|f| f << content }
2292
2637
  at_end { File.unlink filename }
2293
2638
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2294
- pr = proc { _require_action_class './test_6lv7l:Ex_6lv7l::Sample' }
2639
+ pr = proc { require_action_class './test_6lv7l:Ex_6lv7l::Sample' }
2295
2640
  _.ok {pr}.raise?(NameError, "'./test_6lv7l:Ex_6lv7l::Sample': class not found (Ex_6lv7l::Sample).")
2296
2641
  end
2297
2642
  end
@@ -2302,7 +2647,7 @@ Oktest.scope do
2302
2647
  File.open(filename, 'w') {|f| f << content }
2303
2648
  at_end { File.unlink filename }
2304
2649
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2305
- pr = proc { _require_action_class './test_thf7t:Ex_thf7t' }
2650
+ pr = proc { require_action_class './test_thf7t:Ex_thf7t' }
2306
2651
  _.ok {pr}.raise?(TypeError, "'./test_thf7t:Ex_thf7t': class name expected but got \"XXX\".")
2307
2652
  end
2308
2653
  end
@@ -2313,7 +2658,7 @@ Oktest.scope do
2313
2658
  File.open(filename, 'w') {|f| f << content }
2314
2659
  at_end { File.unlink filename }
2315
2660
  K8::ActionMapping.new([]).instance_exec(self) do |_|
2316
- pr = proc { _require_action_class './test_yqcgx:Ex_yqcgx' }
2661
+ pr = proc { require_action_class './test_yqcgx:Ex_yqcgx' }
2317
2662
  _.ok {pr}.raise?(TypeError, "'./test_yqcgx:Ex_yqcgx': expected subclass of K8::Action but not.")
2318
2663
  end
2319
2664
  end
@@ -2373,6 +2718,30 @@ Oktest.scope do
2373
2718
  end
2374
2719
 
2375
2720
 
2721
+ topic 'K8::REQUEST_CLASS=' do
2722
+
2723
+ spec "[!7uqb4] changes default request class." do
2724
+ original = K8::RackApplication::REQUEST_CLASS
2725
+ at_end { K8::RackApplication.REQUEST_CLASS = original }
2726
+ K8::RackApplication.REQUEST_CLASS = Array
2727
+ ok {K8::RackApplication::REQUEST_CLASS} == Array
2728
+ end
2729
+
2730
+ end
2731
+
2732
+
2733
+ topic 'K8::RESPONSE_CLASS=' do
2734
+
2735
+ spec "[!c1bd0] changes default response class." do
2736
+ original = K8::RackApplication::RESPONSE_CLASS
2737
+ at_end { K8::RackApplication.RESPONSE_CLASS = original }
2738
+ K8::RackApplication.RESPONSE_CLASS = Hash
2739
+ ok {K8::RackApplication::RESPONSE_CLASS} == Hash
2740
+ end
2741
+
2742
+ end
2743
+
2744
+
2376
2745
  topic '#initialize()' do
2377
2746
 
2378
2747
  spec "[!vkp65] mounts urlpath mappings." do
@@ -2414,14 +2783,83 @@ Oktest.scope do
2414
2783
  end
2415
2784
 
2416
2785
 
2417
- topic '#lookup()' do
2786
+ topic '#find()' do
2418
2787
 
2419
2788
  spec "[!o0rnr] returns action class, action methods, urlpath names and values." do
2420
2789
  |app|
2421
- ret = app.lookup('/api/books/')
2422
- ok {ret} == [BooksAction, {:GET=>:do_index, :POST=>:do_create}, [], []]
2423
- ret = app.lookup('/api/books/123')
2424
- ok {ret} == [BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}, ["id"], [123]]
2790
+ ret = app.find('/api/books/')
2791
+ ok {ret} == [BooksAction, {:GET=>:do_index, :POST=>:do_create}, []]
2792
+ ret = app.find('/api/books/123')
2793
+ ok {ret} == [BooksAction, {:GET=>:do_show, :PUT=>:do_update, :DELETE=>:do_delete}, [123]]
2794
+ end
2795
+
2796
+ end
2797
+
2798
+
2799
+ topic '#lookup()' do
2800
+
2801
+ spec "[!7476i] uses '_method' value of query string as request method when 'POST' method." do
2802
+ |app|
2803
+ tuple = app.lookup(:POST, "/api/books/123", "_method=DELETE")
2804
+ ok {tuple} == [BooksAction, :do_delete, [123]] # found :do_delete
2805
+ end
2806
+
2807
+ spec "[!c0job] redirects only when request method is GET or HEAD." do
2808
+ |app|
2809
+ ## not redirect on :POST
2810
+ pr3 = proc { app.lookup(:POST, "/api/books", "") }
2811
+ ok {pr3}.raise?(K8::HttpException)
2812
+ ex3 = pr3.exception
2813
+ ok {ex3.status_code} == 404
2814
+ end
2815
+
2816
+ spec "[!u1qfv] raises 301 when urlpath not found but found with tailing '/'." do
2817
+ |app|
2818
+ pr = proc { app.lookup(:GET, "/api/books", "") }
2819
+ ok {pr}.raise?(K8::HttpException)
2820
+ ex = pr.exception
2821
+ ok {ex.status_code} == 301
2822
+ ok {ex.response_headers} == {"Location"=>"/api/books/"}
2823
+ end
2824
+
2825
+ spec "[!kbff3] raises 301 when urlpath not found but found without tailing '/'." do
2826
+ |app|
2827
+ pr = proc { app.lookup(:GET, "/api/books/123/", "") }
2828
+ ok {pr}.raise?(K8::HttpException)
2829
+ ex = pr.exception
2830
+ ok {ex.status_code} == 301
2831
+ ok {ex.response_headers} == {"Location"=>"/api/books/123"}
2832
+ end
2833
+
2834
+ spec "[!cgxx4] adds query string to 'Location' header when redirecting." do
2835
+ |app|
2836
+ pr = proc { app.lookup(:GET, "/api/books", "x=1&y=2") }
2837
+ ok {pr}.raise?(K8::HttpException)
2838
+ ex = pr.exception
2839
+ ok {ex.status_code} == 301
2840
+ ok {ex.response_headers} == {"Location"=>"/api/books/?x=1&y=2"}
2841
+ end
2842
+
2843
+ spec "[!hdy1f] raises HTTP 404 when urlpath not found." do
2844
+ |app|
2845
+ pr = proc { app.lookup(:GET, "/api/book/comments", "") }
2846
+ ok {pr}.raise?(K8::HttpException)
2847
+ ex = pr.exception
2848
+ ok {ex.status_code} == 404
2849
+ end
2850
+
2851
+ spec "[!0znwr] uses 'GET' method to find action when request method is 'HEAD'." do
2852
+ |app|
2853
+ tuple = app.lookup(:HEAD, "/api/books/123")
2854
+ ok {tuple} == [BooksAction, :do_show, [123]]
2855
+ end
2856
+
2857
+ spec "[!bfpav] raises HTTP 405 when urlpath found but request method not allowed." do
2858
+ |app|
2859
+ pr = proc { app.lookup(:POST, "/api/books/123", "") }
2860
+ ok {pr}.raise?(K8::HttpException)
2861
+ ex = pr.exception
2862
+ ok {ex.status_code} == 405
2425
2863
  end
2426
2864
 
2427
2865
  end
@@ -2456,7 +2894,7 @@ Oktest.scope do
2456
2894
  status, headers, body = app.call(env)
2457
2895
  ok {status} == 301
2458
2896
  ok {headers['Location']} == "/api/books/"
2459
- ok {body} == []
2897
+ ok {body} == ["<div>\n<h2>301 Moved Permanently</h2>\n<p></p>\n</div>\n"]
2460
2898
  end
2461
2899
 
2462
2900
  spec "[!02dow] returns 301 when urlpath not found but found without tailing '/'." do
@@ -2465,20 +2903,7 @@ Oktest.scope do
2465
2903
  status, headers, body = app.call(env)
2466
2904
  ok {status} == 301
2467
2905
  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"
2906
+ ok {body} == ["<div>\n<h2>301 Moved Permanently</h2>\n<p></p>\n</div>\n"]
2482
2907
  end
2483
2908
 
2484
2909
  spec "[!vz07j] redirects only when request method is GET or HEAD." do
@@ -2494,106 +2919,113 @@ Oktest.scope do
2494
2919
  ok {headers['Location']} == nil
2495
2920
  end
2496
2921
 
2497
- end
2498
-
2499
-
2500
- topic '#handle_request()' do
2501
-
2502
- spec "[!0fgbd] finds action class and invokes action method with urlpath params." do
2503
- |app|
2504
- env = new_env("GET", "/api/books/123")
2505
- app.instance_exec(self) do |_|
2506
- tuple = handle_request(K8::Request.new(env), K8::Response.new)
2507
- _.ok {tuple}.is_a?(Array)
2508
- status, headers, body = tuple
2509
- _.ok {status} == 200
2510
- _.ok {body} == ["<show:123(Fixnum)>"]
2511
- _.ok {headers} == {
2512
- "Content-Length" => "18",
2513
- "Content-Type" => "text/html; charset=utf-8",
2514
- }
2515
- end
2516
- end
2517
-
2518
2922
  spec "[!l6kmc] uses 'GET' method to find action when request method is 'HEAD'." do
2519
2923
  |app|
2520
2924
  env = new_env("HEAD", "/api/books/123")
2521
- app.instance_exec(self) do |_|
2522
- tuple = handle_request(K8::Request.new(env), K8::Response.new)
2523
- status, headers, body = tuple
2524
- _.ok {status} == 200
2525
- _.ok {body} == [""]
2526
- _.ok {headers} == {
2527
- "Content-Length" => "18",
2528
- "Content-Type" => "text/html; charset=utf-8",
2529
- }
2530
- end
2925
+ status, headers, body = app.call(env)
2926
+ ok {status} == 200
2927
+ ok {body} == [""]
2928
+ ok {headers} == {
2929
+ "Content-Length" => "18",
2930
+ "Content-Type" => "text/html; charset=utf-8",
2931
+ }
2531
2932
  end
2532
2933
 
2533
2934
  spec "[!4vmd3] uses '_method' value of query string as request method when 'POST' method." do
2534
2935
  |app|
2535
2936
  env = new_env("POST", "/api/books/123", query: {"_method"=>"DELETE"})
2536
- app.instance_exec(self) do |_|
2537
- tuple = handle_request(K8::Request.new(env), K8::Response.new)
2538
- status, headers, body = tuple
2539
- _.ok {status} == 200
2540
- _.ok {body} == ["<delete:123(Fixnum)>"] # do_delete() caled
2541
- end
2937
+ status, headers, body = app.call(env)
2938
+ ok {status} == 200
2939
+ ok {body} == ["<delete:123(Fixnum)>"] # do_delete() caled
2542
2940
  end
2543
2941
 
2544
2942
  spec "[!vdllr] clears request and response if possible." do
2545
2943
  |app|
2546
- req = K8::Request.new(new_env("GET", "/"))
2547
- resp = K8::Response.new()
2548
- req_clear = false
2549
- (class << req; self; end).__send__(:define_method, :clear) { req_clear = true }
2550
- resp_clear = false
2551
- (class << resp; self; end).__send__(:define_method, :clear) { resp_clear = true }
2944
+ reqclass = K8::RackApplication::REQUEST_CLASS
2945
+ respclass = K8::RackApplication::RESPONSE_CLASS
2946
+ K8::RackApplication.module_eval do
2947
+ remove_const :REQUEST_CLASS
2948
+ remove_const :RESPONSE_CLASS
2949
+ end
2950
+ $req_clear = $resp_clear = false
2951
+ K8::RackApplication::REQUEST_CLASS = Class.new(reqclass) do
2952
+ def clear; $req_clear = true; end
2953
+ end
2954
+ K8::RackApplication::RESPONSE_CLASS = Class.new(respclass) do
2955
+ def clear; $resp_clear = true; end
2956
+ end
2957
+ at_end do
2958
+ K8::RackApplication.REQUEST_CLASS = reqclass
2959
+ K8::RackApplication.RESPONSE_CLASS = respclass
2960
+ $req_clear = nil
2961
+ $resp_clear = nil
2962
+ end
2552
2963
  #
2553
- app.instance_exec(self) do |_|
2554
- tuple = handle_request(req, resp)
2555
- _.ok {req_clear} == true
2556
- _.ok {resp_clear} == true
2964
+ env = new_env("GET", "/")
2965
+ ok {$req_clear} == false
2966
+ ok {$resp_clear} == false
2967
+ app.call(env)
2968
+ _ = self
2969
+ K8::RackApplication::REQUEST_CLASS.class_eval do
2970
+ _.ok {$req_clear} == true
2557
2971
  end
2558
- end
2559
-
2560
- spec "[!9wp9z] returns empty body when request method is HEAD." do
2561
- |app|
2562
- env = new_env("HEAD", "/api/books/123")
2563
- app.instance_exec(self) do |_|
2564
- tuple = handle_request(K8::Request.new(env), K8::Response.new)
2565
- status, headers, body = tuple
2566
- _.ok {body} == [""]
2972
+ K8::RackApplication::RESPONSE_CLASS.class_eval do
2973
+ _.ok {$resp_clear} == true
2567
2974
  end
2568
2975
  end
2569
2976
 
2570
2977
  spec "[!rz13i] returns HTTP 404 when urlpath not found." do
2571
2978
  |app|
2572
2979
  env = new_env("GET", "/api/book/comments")
2980
+ status, headers, body = app.call(env)
2981
+ ok {status} == 404
2982
+ ok {headers} == {
2983
+ "Content-Length" => "44",
2984
+ "Content-Type" => "text/html;charset=utf-8",
2985
+ }
2986
+ ok {body} == ["<div>\n<h2>404 Not Found</h2>\n<p></p>\n</div>\n"]
2987
+ end
2988
+
2989
+ spec "[!rv3cf] returns HTTP 405 when urlpath found but request method not allowed." do
2990
+ |app|
2991
+ env = new_env("POST", "/api/books/123")
2992
+ status, headers, body = app.call(env)
2993
+ ok {status} == 405
2994
+ ok {headers} == {
2995
+ "Content-Length" => "53",
2996
+ "Content-Type" => "text/html;charset=utf-8",
2997
+ }
2998
+ ok {body} == ["<div>\n<h2>405 Method Not Allowed</h2>\n<p></p>\n</div>\n"]
2999
+ end
3000
+
3001
+ end
3002
+
3003
+
3004
+ topic '#handle_request()' do
3005
+
3006
+ spec "[!0fgbd] finds action class and invokes action method with urlpath params." do
3007
+ |app|
3008
+ env = new_env("GET", "/api/books/123")
2573
3009
  app.instance_exec(self) do |_|
2574
- tuple = handle_request(K8::Request.new(env), K8::Response.new)
3010
+ tuple = handle_request(K8::RackRequest.new(env), K8::RackResponse.new)
3011
+ _.ok {tuple}.is_a?(Array)
2575
3012
  status, headers, body = tuple
2576
- _.ok {status} == 404
2577
- _.ok {body} == ["<div>\n<h2>404 Not Found</h2>\n<p></p>\n</div>\n"]
3013
+ _.ok {status} == 200
3014
+ _.ok {body} == ["<show:123(Fixnum)>"]
2578
3015
  _.ok {headers} == {
2579
- "Content-Length" => "44",
2580
- "Content-Type" => "text/html;charset=utf-8",
3016
+ "Content-Length" => "18",
3017
+ "Content-Type" => "text/html; charset=utf-8",
2581
3018
  }
2582
3019
  end
2583
3020
  end
2584
3021
 
2585
- spec "[!rv3cf] returns HTTP 405 when urlpath found but request method not allowed." do
3022
+ spec "[!9wp9z] returns empty body when request method is HEAD." do
2586
3023
  |app|
2587
- env = new_env("POST", "/api/books/123")
3024
+ env = new_env("HEAD", "/api/books/123")
2588
3025
  app.instance_exec(self) do |_|
2589
- tuple = handle_request(K8::Request.new(env), K8::Response.new)
3026
+ tuple = handle_request(K8::RackRequest.new(env), K8::RackResponse.new)
2590
3027
  status, headers, body = tuple
2591
- _.ok {status} == 405
2592
- _.ok {body} == ["<div>\n<h2>405 Method Not Allowed</h2>\n<p></p>\n</div>\n"]
2593
- _.ok {headers} == {
2594
- "Content-Length" => "53",
2595
- "Content-Type" => "text/html;charset=utf-8",
2596
- }
3028
+ _.ok {body} == [""]
2597
3029
  end
2598
3030
  end
2599
3031
 
@@ -2652,364 +3084,6 @@ Oktest.scope do
2652
3084
  end
2653
3085
 
2654
3086
 
2655
- topic K8::SecretValue do
2656
-
2657
-
2658
- topic '#initialize()' do
2659
-
2660
- spec "[!fbwnh] takes environment variable name." do
2661
- obj = K8::SecretValue.new('DB_PASS')
2662
- ok {obj.name} == 'DB_PASS'
2663
- end
2664
-
2665
- end
2666
-
2667
-
2668
- topic '#value()' do
2669
-
2670
- spec "[!gg06v] returns environment variable value." do
2671
- obj = K8::SecretValue.new('TEST_HOMHOM')
2672
- ok {obj.value} == nil
2673
- ENV['TEST_HOMHOM'] = 'homhom'
2674
- ok {obj.value} == 'homhom'
2675
- end
2676
-
2677
- end
2678
-
2679
-
2680
- topic '#to_s()' do
2681
-
2682
- spec "[!7ymqq] returns '<SECRET>' string when name not eixst." do
2683
- ok {K8::SecretValue.new.to_s} == "<SECRET>"
2684
- end
2685
-
2686
- spec "[!x6edf] returns 'ENV[<name>]' string when name exists." do
2687
- ok {K8::SecretValue.new('DB_PASS').to_s} == "ENV['DB_PASS']"
2688
- end
2689
-
2690
- end
2691
-
2692
-
2693
- topic '#inspect()' do
2694
-
2695
- spec "[!j27ji] 'inspect()' is alias of 'to_s()'." do
2696
- ok {K8::SecretValue.new('DB_PASS').inspect} == "ENV['DB_PASS']"
2697
- end
2698
-
2699
- end
2700
-
2701
-
2702
- topic '#[](name)' do
2703
-
2704
- spec "[!jjqmn] creates new instance object with name." do
2705
- obj = K8::SecretValue.new['DB_PASSWORD']
2706
- ok {obj}.is_a?(K8::SecretValue)
2707
- ok {obj.name} == 'DB_PASSWORD'
2708
- end
2709
-
2710
- end
2711
-
2712
-
2713
- end
2714
-
2715
-
2716
- topic K8::BaseConfig do
2717
-
2718
-
2719
- topic '#initialize()' do
2720
-
2721
- spec "[!vvd1n] copies key and values from class object." do
2722
- class C01 < K8::BaseConfig
2723
- add :haruhi , 'C' , "Suzumiya"
2724
- add :mikuru , 'E' , "Asahina"
2725
- add :yuki , 'A' , "Nagato"
2726
- end
2727
- c = C01.new
2728
- c.instance_exec(self) do |_|
2729
- _.ok {@haruhi} == 'C'
2730
- _.ok {@mikuru} == 'E'
2731
- _.ok {@yuki} == 'A'
2732
- end
2733
- end
2734
-
2735
- spec "[!6dilv] freezes self and class object if 'freeze:' is true." do
2736
- class C02 < K8::BaseConfig
2737
- add :haruhi , 'C' , "Suzumiya"
2738
- add :mikuru , 'E' , "Asahina"
2739
- add :yuki , 'A' , "Nagato"
2740
- end
2741
- ## when freeze: false
2742
- c = C02.new(freeze: false)
2743
- pr = proc { c.instance_variable_set('@yuki', 'B') }
2744
- ok {pr}.NOT.raise?(Exception)
2745
- pr = proc { C02.class_eval { put :yuki, 'B' } }
2746
- ok {pr}.NOT.raise?(Exception)
2747
- ## when freeze: true
2748
- c = C02.new
2749
- pr = proc { c.instance_variable_set('@yuki', 'B') }
2750
- ok {pr}.raise?(RuntimeError, "can't modify frozen C02")
2751
- pr = proc { C02.class_eval { put :yuki, 'B' } }
2752
- ok {pr}.raise?(RuntimeError, "can't modify frozen class")
2753
- end
2754
-
2755
- case_when "[!xok12] when value is SECRET..." do
2756
-
2757
- spec "[!a4a4p] raises error when key not specified." do
2758
- class C03 < K8::BaseConfig
2759
- add :db_pass , SECRET, "db password"
2760
- end
2761
- pr = proc { C03.new }
2762
- ok {pr}.raise?(K8::ConfigError, "config 'db_pass' should be set, but not.")
2763
- end
2764
-
2765
- spec "[!w4yl7] raises error when ENV value not specified." do
2766
- class C04 < K8::BaseConfig
2767
- add :db_pass1 , SECRET['DB_PASS1'], "db password"
2768
- end
2769
- ok {ENV['DB_PASS1']} == nil
2770
- pr = proc { C04.new }
2771
- ok {pr}.raise?(K8::ConfigError, )
2772
- end
2773
-
2774
- spec "[!he20d] get value from ENV." do
2775
- class C05 < K8::BaseConfig
2776
- add :db_pass1 , SECRET['DB_PASS1'], "db password"
2777
- end
2778
- begin
2779
- ENV['DB_PASS1'] = 'homhom'
2780
- pr = proc { C05.new }
2781
- ok {pr}.NOT.raise?(Exception)
2782
- ok {C05.new.db_pass1} == 'homhom'
2783
- ensure
2784
- ENV['DB_PASS1'] = nil
2785
- end
2786
- end
2787
-
2788
- end
2789
-
2790
- end
2791
-
2792
-
2793
- topic '.has?()' do
2794
-
2795
- spec "[!dv87n] returns true iff key is set." do
2796
- class C11 < K8::BaseConfig
2797
- @result1 = has? :foo
2798
- put :foo, 1
2799
- @result2 = has? :foo
2800
- end
2801
- ok {C11.instance_variable_get('@result1')} == false
2802
- ok {C11.instance_variable_get('@result2')} == true
2803
- end
2804
-
2805
- end
2806
-
2807
-
2808
- topic '.put()' do
2809
-
2810
- spec "[!h9b47] defines getter method." do
2811
- class C21 < K8::BaseConfig
2812
- put :hom, 123, "HomHom"
2813
- end
2814
- ok {C21.instance_methods}.include?(:hom)
2815
- ok {C21.new.hom} == 123
2816
- end
2817
-
2818
- spec "[!ncwzt] stores key with value, description and secret flag." do
2819
- class C22 < K8::BaseConfig
2820
- put :hom, 123, "HomHom"
2821
- put :hom2, SECRET, "Secret HomHom"
2822
- end
2823
- ok {C22.instance_variable_get('@__all')} == {
2824
- :hom => [123, "HomHom", false],
2825
- :hom2 => [K8::BaseConfig::SECRET, "Secret HomHom", true],
2826
- }
2827
- end
2828
-
2829
- spec "[!mun1v] keeps secret flag." do
2830
- class C23 < K8::BaseConfig
2831
- put :haruhi , 'C' , "Suzumiya"
2832
- put :mikuru , SECRET, "Asahina"
2833
- put :yuki , SECRET, "Nagato"
2834
- end
2835
- class C23
2836
- put :mikuru , 'F'
2837
- end
2838
- ok {C23.instance_variable_get('@__all')} == {
2839
- :haruhi => ['C', "Suzumiya", false],
2840
- :mikuru => ['F', "Asahina", true],
2841
- :yuki => [K8::BaseConfig::SECRET, "Nagato", true],
2842
- }
2843
- end
2844
-
2845
- end
2846
-
2847
-
2848
- topic '.add()' do
2849
-
2850
- spec "[!envke] raises error when already added." do
2851
- class C31 < K8::BaseConfig
2852
- add :hom, 123, "HomHom"
2853
- @ex = nil
2854
- begin
2855
- add :hom, 456, "HomHom"
2856
- rescue => ex
2857
- @ex = ex
2858
- end
2859
- end
2860
- ex = C31.instance_variable_get('@ex')
2861
- ok {ex} != nil
2862
- ok {ex}.is_a?(K8::ConfigError)
2863
- ok {ex.message} == "add(:hom, 456): cannot add because already added; use set() or put() instead."
2864
- end
2865
-
2866
- spec "[!6cmb4] adds new key, value and desc." do
2867
- class C32 < K8::BaseConfig
2868
- add :hom, 123, "HomHom"
2869
- add :hom2, 'HOM'
2870
- end
2871
- all = C32.instance_variable_get('@__all')
2872
- ok {all} == {:hom=>[123, "HomHom", false], :hom2=>['HOM', nil, false]}
2873
- end
2874
-
2875
- end
2876
-
2877
-
2878
- topic '.set()' do
2879
-
2880
- spec "[!2yis0] raises error when not added yet." do
2881
- class C41 < K8::BaseConfig
2882
- @ex = nil
2883
- begin
2884
- set :hom, 123, "HomHom"
2885
- rescue => ex
2886
- @ex = ex
2887
- end
2888
- end
2889
- ex = C41.instance_variable_get('@ex')
2890
- ok {ex} != nil
2891
- ok {ex}.is_a?(K8::ConfigError)
2892
- ok {ex.message} == "set(:hom, 123): cannot set because not added yet; use add() or put() instead."
2893
- end
2894
-
2895
- spec "[!3060g] sets key, value and desc." do
2896
- class C42 < K8::BaseConfig
2897
- add :hom, 123, "HomHom"
2898
- end
2899
- class C42
2900
- set :hom, 456
2901
- end
2902
- all = C42.instance_variable_get('@__all')
2903
- ok {all} == {:hom=>[456, "HomHom", false]}
2904
- end
2905
-
2906
- end
2907
-
2908
-
2909
- topic '.each()' do
2910
-
2911
- spec "[!iu88i] yields with key, value, desc and secret flag." do
2912
- class C51 < K8::BaseConfig
2913
- add :haruhi , 'C' , "Suzumiya"
2914
- add :mikuru , SECRET, "Asahina"
2915
- add :yuki , 'A' , "Nagato"
2916
- end
2917
- class C51
2918
- set :mikuru , 'F'
2919
- add :sasaki , 'B'
2920
- end
2921
- #
2922
- arr = []
2923
- C51.each {|*args| arr << args }
2924
- ok {arr} == [
2925
- [:haruhi, 'C', "Suzumiya", false],
2926
- [:mikuru, 'F', "Asahina", true],
2927
- [:yuki, 'A', "Nagato", false],
2928
- [:sasaki, 'B', nil, false],
2929
- ]
2930
- end
2931
-
2932
- end
2933
-
2934
-
2935
- topic '.get()' do
2936
-
2937
- spec "[!zlhnp] returns value corresponding to key." do
2938
- class C61 < K8::BaseConfig
2939
- add :haruhi , 'C' , "Suzumiya"
2940
- add :mikuru , 'E' , "Asahina"
2941
- add :yuki , 'A' , "Nagato"
2942
- end
2943
- class C61
2944
- set :mikuru , 'F'
2945
- add :sasaki , 'B'
2946
- end
2947
- ok {C61.get(:haruhi)} == 'C'
2948
- ok {C61.get(:mikuru)} == 'F'
2949
- ok {C61.get(:yuki)} == 'A'
2950
- ok {C61.get(:sasaki)} == 'B'
2951
- end
2952
-
2953
- spec "[!o0k05] returns default value (=nil) when key is not added." do
2954
- class C62 < K8::BaseConfig
2955
- add :haruhi , 'C' , "Suzumiya"
2956
- add :mikuru , 'E' , "Asahina"
2957
- add :yuki , 'A' , "Nagato"
2958
- end
2959
- ok {C62.get(:sasaki)} == nil
2960
- ok {C62.get(:sasaki, "")} == ""
2961
- end
2962
-
2963
- end
2964
-
2965
-
2966
- topic '[](key)' do
2967
-
2968
- spec "[!jn9l5] returns value corresponding to key." do
2969
- class C71 < K8::BaseConfig
2970
- add :haruhi , 'C' , "Suzumiya"
2971
- add :mikuru , 'E' , "Asahina"
2972
- add :yuki , 'A' , "Nagato"
2973
- end
2974
- class C71
2975
- set :mikuru , 'F'
2976
- add :sasaki , 'B'
2977
- end
2978
- c = C71.new
2979
- ok {c[:haruhi]} == 'C'
2980
- ok {c[:mikuru]} == 'F'
2981
- ok {c[:yuki]} == 'A'
2982
- ok {c[:sasaki]} == 'B'
2983
- end
2984
-
2985
- end
2986
-
2987
-
2988
- topic '#get_all()' do
2989
-
2990
- spec "[!4ik3c] returns all keys and values which keys start with prefix as hash object." do
2991
- class C81 < K8::BaseConfig
2992
- add :session_cookie_name , 'rack.sess'
2993
- add :session_cookie_expires , 30*60*60
2994
- add :session_cookie_secure , true
2995
- add :name , 'Homhom'
2996
- add :secure , false
2997
- end
2998
- #
2999
- c = C81.new
3000
- ok {c.get_all(:session_cookie_)} == {
3001
- :name => 'rack.sess',
3002
- :expires => 30*60*60,
3003
- :secure => true,
3004
- }
3005
- end
3006
-
3007
- end
3008
-
3009
-
3010
- end
3011
-
3012
-
3013
3087
  end
3014
3088
 
3015
3089