keight 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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