dassets 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.md +56 -2
  2. data/bin/dassets +7 -0
  3. data/dassets.gemspec +9 -3
  4. data/lib/dassets/asset_file.rb +65 -0
  5. data/lib/dassets/cli.rb +109 -0
  6. data/lib/dassets/digests_file.rb +70 -0
  7. data/lib/dassets/root_path.rb +12 -0
  8. data/lib/dassets/runner/cache_command.rb +46 -0
  9. data/lib/dassets/runner/digest_command.rb +65 -0
  10. data/lib/dassets/runner.rb +42 -0
  11. data/lib/dassets/server/request.rb +48 -0
  12. data/lib/dassets/server/response.rb +36 -0
  13. data/lib/dassets/server.rb +37 -0
  14. data/lib/dassets/version.rb +1 -1
  15. data/lib/dassets.rb +32 -2
  16. data/test/helper.rb +3 -1
  17. data/test/support/app/assets/.digests +4 -0
  18. data/test/support/app/assets/public/file1.txt +1 -0
  19. data/test/support/app/assets/public/file2.txt +1 -0
  20. data/test/support/app/assets/public/grumpy_cat.jpg +0 -0
  21. data/test/support/app/assets/public/nested/file3.txt +0 -0
  22. data/test/support/app.rb +10 -0
  23. data/test/support/app_public/.gitkeep +0 -0
  24. data/test/support/config/assets.rb +7 -0
  25. data/test/support/example.digests +3 -0
  26. data/test/support/public/file1-daa05c683a4913b268653f7a7e36a5b4.txt +1 -0
  27. data/test/support/public/file2-9bbe1047cffbb590f59e0e5aeff46ae4.txt +1 -0
  28. data/test/support/public/grumpy_cat-b0d1f399a916f7a25c4c0f693c619013.jpg +0 -0
  29. data/test/support/public/nested/file3-d41d8cd98f00b204e9800998ecf8427e.txt +0 -0
  30. data/test/system/rack_tests.rb +78 -0
  31. data/test/unit/asset_file_tests.rb +76 -0
  32. data/test/unit/config_tests.rb +27 -0
  33. data/test/unit/dassets_tests.rb +49 -0
  34. data/test/unit/digests_file_tests.rb +90 -0
  35. data/test/unit/runner/cache_command_tests.rb +62 -0
  36. data/test/unit/runner/digest_command_tests.rb +83 -0
  37. data/test/unit/runner_tests.rb +29 -0
  38. data/test/unit/server/request_tests.rb +76 -0
  39. data/test/unit/server/response_tests.rb +70 -0
  40. data/test/unit/server_tests.rb +17 -0
  41. metadata +130 -10
@@ -0,0 +1,4 @@
1
+ file1.txt,daa05c683a4913b268653f7a7e36a5b4
2
+ file2.txt,9bbe1047cffbb590f59e0e5aeff46ae4
3
+ grumpy_cat.jpg,b0d1f399a916f7a25c4c0f693c619013
4
+ nested/file3.txt,d41d8cd98f00b204e9800998ecf8427e
@@ -0,0 +1 @@
1
+ file1.txt
@@ -0,0 +1 @@
1
+ file2.txt
File without changes
@@ -0,0 +1,10 @@
1
+ require 'sinatra/base'
2
+
3
+ class SinatraApp < Sinatra::Base
4
+
5
+ configure do
6
+ set :root, File.expand_path('..', __FILE__)
7
+ set :public_dir, File.expand_path('./app_public', __FILE__)
8
+ end
9
+
10
+ end
File without changes
@@ -0,0 +1,7 @@
1
+ require 'dassets'
2
+
3
+ Dassets.configure do |c|
4
+ c.root_path File.expand_path("../..", __FILE__)
5
+
6
+ end
7
+
@@ -0,0 +1,3 @@
1
+ /path/to/file1,abc123
2
+ /path/to/file2,123abc
3
+ /path/to/file3,a1b2c3
@@ -0,0 +1,78 @@
1
+ require 'assert'
2
+ require 'assert-rack-test'
3
+ require 'test/support/app'
4
+ require 'dassets/server'
5
+
6
+ module Dassets
7
+
8
+ class RackTests < Assert::Context
9
+ include Assert::Rack::Test
10
+
11
+ desc "the middleware in a rack app"
12
+ setup do
13
+ Dassets.init
14
+ app.use Dassets::Server
15
+ end
16
+ teardown do
17
+ Dassets.reset
18
+ end
19
+
20
+ def app
21
+ @app ||= SinatraApp
22
+ end
23
+
24
+ end
25
+
26
+ class SuccessTests < RackTests
27
+ desc "requesting an existing asset file"
28
+
29
+ should "return a successful response" do
30
+ resp = get '/file1-daa05c683a4913b268653f7a7e36a5b4.txt'
31
+ assert_equal 200, resp.status
32
+ assert_equal Dassets['/file1.txt'].content, resp.body
33
+ end
34
+
35
+ should "return a successful response with no body on HEAD requests" do
36
+ resp = head '/file2-9bbe1047cffbb590f59e0e5aeff46ae4.txt'
37
+ assert_equal 200, resp.status
38
+ assert_equal Dassets['/file2.txt'].size.to_s, resp.headers['Content-Length']
39
+ assert_empty resp.body
40
+ end
41
+
42
+ end
43
+
44
+ class NotModifiedTests < RackTests
45
+ desc "requesting an existing asset file that has not been modified"
46
+ setup do
47
+ @resp = get('/file1-daa05c683a4913b268653f7a7e36a5b4.txt', {}, {
48
+ 'HTTP_IF_MODIFIED_SINCE' => Dassets['/file1.txt'].mtime.to_s
49
+ })
50
+ end
51
+
52
+ should "return a successful response" do
53
+ assert_equal 304, @resp.status
54
+ assert_empty @resp.body
55
+ end
56
+
57
+ end
58
+
59
+ class NotFoundTests < RackTests
60
+ desc "requesting an non-existing asset file"
61
+
62
+ should "return a not found response" do
63
+ resp = get '/file1-daa05c683a4913b268.txt'
64
+ assert_equal 404, resp.status
65
+
66
+ get '/file1-.txt'
67
+ assert_equal 404, resp.status
68
+
69
+ get '/file1.txt'
70
+ assert_equal 404, resp.status
71
+
72
+ get '/something-not-found'
73
+ assert_equal 404, resp.status
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,76 @@
1
+ require 'assert'
2
+ require 'dassets/asset_file'
3
+
4
+ class Dassets::AssetFile
5
+
6
+ class BaseTests < Assert::Context
7
+ desc "Dassets::AssetFile"
8
+ setup do
9
+ @asset_file = Dassets::AssetFile.new('file1.txt', 'abc123')
10
+ end
11
+ subject{ @asset_file }
12
+
13
+ should have_cmeths :from_abs_path
14
+ should have_readers :path, :md5, :dirname, :extname, :basename
15
+ should have_readers :files_path, :cache_path, :href
16
+ should have_imeth :content, :mtime, :size, :mime_type, :exists?, :==
17
+
18
+ should "know its given path and md5" do
19
+ assert_equal 'file1.txt', subject.path
20
+ assert_equal 'abc123', subject.md5
21
+ end
22
+
23
+ should "know its dirname, extname, and basename" do
24
+ assert_equal '.', subject.dirname
25
+ assert_equal '.txt', subject.extname
26
+ assert_equal 'file1', subject.basename
27
+ end
28
+
29
+ should "build it's files_path from the path" do
30
+ assert_equal "#{Dassets.config.files_path}/file1.txt", subject.files_path
31
+
32
+ nested = Dassets::AssetFile.new('nested/file1.txt', 'abc123')
33
+ assert_equal "#{Dassets.config.files_path}/nested/file1.txt", nested.files_path
34
+ end
35
+
36
+ should "build it's cache_path from the path and the md5" do
37
+ assert_equal "file1-abc123.txt", subject.cache_path
38
+
39
+ nested = Dassets::AssetFile.new('nested/file1.txt', 'abc123')
40
+ assert_equal "nested/file1-abc123.txt", nested.cache_path
41
+ end
42
+
43
+ should "build it's href from the cache path" do
44
+ assert_equal "/file1-abc123.txt", subject.href
45
+
46
+ nested = Dassets::AssetFile.new('nested/file1.txt', 'abc123')
47
+ assert_equal "/nested/file1-abc123.txt", nested.href
48
+ end
49
+
50
+ should "be created from absolute file paths and have md5 computed" do
51
+ abs_file_path = File.join(Dassets.config.files_path, 'file1.txt')
52
+ exp_md5 = 'daa05c683a4913b268653f7a7e36a5b4'
53
+ file = Dassets::AssetFile.from_abs_path(abs_file_path)
54
+
55
+ assert_equal 'file1.txt', file.path
56
+ assert_equal exp_md5, file.md5
57
+ end
58
+
59
+ should "know it's content, mtime, size, mime_type, and if it exists" do
60
+ assert_equal "file1.txt\n", subject.content
61
+ assert_equal File.mtime(subject.files_path).httpdate, subject.mtime
62
+ assert_equal File.size?(subject.files_path), subject.size
63
+ assert_equal "text/plain", subject.mime_type
64
+ assert subject.exists?
65
+
66
+ null_file = Dassets::AssetFile.new('', '')
67
+ assert_nil null_file.content
68
+ assert_nil null_file.mtime
69
+ assert_nil null_file.size
70
+ assert_nil null_file.mime_type
71
+ assert_not null_file.exists?
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,27 @@
1
+ require 'assert'
2
+ require 'ns-options/assert_macros'
3
+ require 'dassets'
4
+
5
+ class Dassets::Config
6
+
7
+ class BaseTests < Assert::Context
8
+ include NsOptions::AssertMacros
9
+ desc "Dassets::Config"
10
+ subject{ Dassets::Config }
11
+
12
+ should have_option :root_path, Pathname, :required => true
13
+ should have_options :files_path, :digests_file_path
14
+
15
+ should "should use `apps/assets/public` as the default files path" do
16
+ exp_path = Dassets.config.root_path.join("app/assets/public").to_s
17
+ assert_equal exp_path, subject.files_path
18
+ end
19
+
20
+ should "should use `app/assets/.digests` as the default digests file path" do
21
+ exp_path = Dassets.config.root_path.join("app/assets/.digests").to_s
22
+ assert_equal exp_path, subject.digests_file_path
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'assert'
2
+ require 'fileutils'
3
+ require 'dassets'
4
+
5
+ module Dassets
6
+
7
+ class BaseTests < Assert::Context
8
+ desc "Dassets"
9
+ subject{ Dassets }
10
+
11
+ should have_imeths :config, :configure, :init, :digests, :[]
12
+
13
+ should "return its `Config` class with the `config` method" do
14
+ assert_same Config, subject.config
15
+ end
16
+
17
+ should "read/parse the digests on init" do
18
+ subject.reset
19
+ assert_empty subject.digests
20
+
21
+ subject.init
22
+ assert_not_empty subject.digests
23
+ end
24
+
25
+ should "return asset files given a their path using the index operator" do
26
+ subject.init
27
+ file = subject['nested/file3.txt']
28
+
29
+ assert_kind_of Dassets::AssetFile, file
30
+ assert_equal 'nested/file3.txt', file.path
31
+ assert_equal 'd41d8cd98f00b204e9800998ecf8427e', file.md5
32
+
33
+ subject.reset
34
+ end
35
+
36
+ should "return an asset file with no fingerprint if path not in digests" do
37
+ file = subject['path/not/found.txt']
38
+ assert_equal '', file.md5
39
+
40
+ subject.init
41
+ file = subject['path/not/found.txt']
42
+ assert_equal '', file.md5
43
+
44
+ subject.reset
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,90 @@
1
+ require 'assert'
2
+ require 'fileutils'
3
+ require 'dassets/digests_file'
4
+ require 'dassets/asset_file'
5
+
6
+ class Dassets::DigestsFile
7
+
8
+ class BaseTests < Assert::Context
9
+ desc "Dassets::DigestsFile"
10
+ setup do
11
+ @file_path = File.join(Dassets.config.root_path, 'example.digests')
12
+ @digests = Dassets::DigestsFile.new(@file_path)
13
+ end
14
+ subject{ @digests }
15
+
16
+ should have_reader :path
17
+ should have_imeths :asset_files, :asset_file, :to_hash, :save!
18
+ should have_imeths :[], :[]=, :delete, :each, :keys, :values, :empty?
19
+
20
+ should "know its path" do
21
+ assert_equal @file_path, subject.path
22
+ end
23
+
24
+ should "know its asset files" do
25
+ assert_equal subject.keys.size, subject.asset_files.size
26
+ assert_kind_of Dassets::AssetFile, subject.asset_files.first
27
+ end
28
+
29
+ should "get a specific asset file from its data" do
30
+ file = subject.asset_file('/path/to/file1')
31
+
32
+ assert_kind_of Dassets::AssetFile, file
33
+ assert_equal '/path/to/file1', file.path
34
+ assert_equal subject['/path/to/file1'], file.md5
35
+ end
36
+
37
+ should "know whether it is empty or not" do
38
+ assert_not_empty subject
39
+ end
40
+
41
+ should "read values with the index operator" do
42
+ assert_equal 'abc123', subject['/path/to/file1']
43
+ end
44
+
45
+ should "write values with the index operator" do
46
+ subject['/path/to/test'] = 'testytest'
47
+ assert_equal 'testytest', subject['/path/to/test']
48
+ end
49
+
50
+ should "remove values with the delete method" do
51
+ assert_includes '/path/to/file1', subject.keys
52
+
53
+ subject.delete '/path/to/file1'
54
+ assert_not_includes '/path/to/file1', subject.keys
55
+ end
56
+
57
+ should "know its hash representation" do
58
+ exp_hash = {
59
+ "/path/to/file1" => "abc123",
60
+ "/path/to/file2" => "123abc",
61
+ "/path/to/file3" => "a1b2c3"
62
+ }
63
+ subject_to_hash = subject.to_hash
64
+ subject_internal_hash = subject.instance_variable_get("@hash")
65
+
66
+ assert_equal exp_hash, subject_to_hash
67
+ assert_not_equal subject_internal_hash.object_id, subject_to_hash.object_id
68
+ end
69
+
70
+ end
71
+
72
+ class SaveTests < BaseTests
73
+ desc "on save"
74
+ setup do
75
+ FileUtils.mv(@file_path, "#{@file_path}.bak")
76
+ end
77
+ teardown do
78
+ FileUtils.mv("#{@file_path}.bak", @file_path)
79
+ end
80
+
81
+ should "write out the digests to the path" do
82
+ assert_not_file_exists subject.path
83
+ subject.save!
84
+
85
+ assert_file_exists subject.path
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,62 @@
1
+ require 'assert'
2
+ require 'fileutils'
3
+ require 'dassets/runner/cache_command'
4
+
5
+ class Dassets::Runner::CacheCommand
6
+
7
+ class BaseTests < Assert::Context
8
+ desc "Dassets::Runner::CacheCommand"
9
+ setup do
10
+ @cache_root_path = File.join(Dassets.config.root_path, 'public')
11
+ FileUtils.mkdir_p @cache_root_path
12
+ @cmd = Dassets::Runner::CacheCommand.new(@cache_root_path)
13
+ end
14
+ teardown do
15
+ # FileUtils.rm_rf @cache_root_path
16
+ end
17
+ subject{ @cmd }
18
+
19
+ should have_readers :files_root_path, :cache_root_path, :digests_file, :asset_files
20
+
21
+ should "use the config's files path and its files root path" do
22
+ assert_equal Dassets.config.files_path, subject.files_root_path.to_s
23
+ end
24
+
25
+ should "know its given cache root path" do
26
+ assert_equal @cache_root_path, subject.cache_root_path.to_s
27
+ end
28
+
29
+ should "know it's digests file" do
30
+ assert_kind_of Dassets::DigestsFile, subject.digests_file
31
+ end
32
+
33
+ should "get it's asset files from the digests file" do
34
+ assert_equal 4, subject.digests_file.keys.size
35
+ assert_equal 4, subject.asset_files.size
36
+ end
37
+
38
+ should "use AssetFile objs for the asset files" do
39
+ assert_kind_of Dassets::AssetFile, subject.asset_files.first
40
+ end
41
+
42
+ end
43
+
44
+ class RunTests < BaseTests
45
+ desc "on run"
46
+ setup do
47
+ FileUtils.rm_rf(@cache_root_path)
48
+ end
49
+
50
+ should "create the cache root and write the cache files" do
51
+ assert_not_file_exists @cache_root_path.to_s
52
+ subject.run
53
+
54
+ assert_file_exists @cache_root_path.to_s
55
+ subject.asset_files.each do |file|
56
+ assert_file_exists File.join(@cache_root_path, file.cache_path)
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,83 @@
1
+ require 'assert'
2
+ require 'fileutils'
3
+ require 'dassets/runner/digest_command'
4
+
5
+ class Dassets::Runner::DigestCommand
6
+
7
+ class BaseTests < Assert::Context
8
+ desc "Dassets::Runner::DigestCommand"
9
+ setup do
10
+ @cmd = Dassets::Runner::DigestCommand.new([])
11
+ end
12
+ subject{ @cmd }
13
+
14
+ should have_instance_methods :asset_files, :digests_file, :run
15
+
16
+ should "know it's digests file" do
17
+ assert_kind_of Dassets::DigestsFile, subject.digests_file
18
+ end
19
+
20
+ should "get it's asset files from the config path by default" do
21
+ assert_equal 4, subject.asset_files.size
22
+ assert_equal 4, subject.digests_file.keys.size
23
+ end
24
+
25
+ should "get it's asset files from the args if passed" do
26
+ path_string = File.join(Dassets.config.files_path, 'file*')
27
+ digest_cmd = Dassets::Runner::DigestCommand.new([path_string])
28
+
29
+ assert_equal 2, digest_cmd.asset_files.size
30
+ end
31
+
32
+ should "use AssetFile objs for the asset files" do
33
+ assert_kind_of Dassets::AssetFile, subject.asset_files.first
34
+ end
35
+
36
+ end
37
+
38
+ class RunTests < BaseTests
39
+ desc "on run"
40
+ setup do
41
+ @cmd.run
42
+ @addfile = 'addfile.txt'
43
+ @rmfile = 'file1.txt'
44
+ @updfile = 'file2.txt'
45
+ @addfile_path = File.join(File.join(Dassets.config.files_path, @addfile))
46
+ @rmfile_path = File.join(File.join(Dassets.config.files_path, @rmfile))
47
+ @updfile_path = File.join(File.join(Dassets.config.files_path, @updfile))
48
+
49
+ @rmfilecontents = File.read(@rmfile_path)
50
+ @updfilecontents = File.read(@updfile_path)
51
+ @orig_updfile_md5 = subject.digests_file[@updfile]
52
+
53
+ FileUtils.touch @addfile_path
54
+ FileUtils.rm @rmfile_path
55
+ File.open(@updfile_path, "w+"){ |f| f.write('an update') }
56
+ end
57
+ teardown do
58
+ File.open(@updfile_path, "w"){ |f| f.write @updfilecontents }
59
+ File.open(@rmfile_path, "w"){ |f| f.write @rmfilecontents }
60
+ FileUtils.rm @addfile_path
61
+ end
62
+
63
+ should "update the digests_file on run" do
64
+ assert_equal 4, subject.digests_file.keys.size
65
+ assert_not_includes @addfile, subject.digests_file.keys
66
+ assert_includes @rmfile, subject.digests_file.keys
67
+ assert_equal @orig_updfile_md5, subject.digests_file[@updfile]
68
+
69
+ # recreate the cmd to reload asset files
70
+ @cmd = Dassets::Runner::DigestCommand.new([])
71
+ # run without writing the file
72
+ subject.run(false)
73
+
74
+ # see the add, update and removal
75
+ assert_equal 4, subject.digests_file.keys.size
76
+ assert_includes @addfile, subject.digests_file.keys
77
+ assert_not_includes @rmfile, subject.digests_file.keys
78
+ assert_not_equal @orig_updfile_md5, subject.digests_file[@updfile]
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,29 @@
1
+ require 'assert'
2
+ require 'pathname'
3
+ require 'dassets/runner'
4
+
5
+ class Dassets::Runner
6
+
7
+ class BaseTests < Assert::Context
8
+ desc "Dassets::Runner"
9
+ setup do
10
+ @runner = Dassets::Runner.new(['null', 1, 2], 'some' => 'opts')
11
+ end
12
+ subject{ @runner }
13
+
14
+ should have_readers :cmd_name, :cmd_args, :opts
15
+
16
+ should "know its cmd, cmd_args, and opts" do
17
+ assert_equal 'null', subject.cmd_name
18
+ assert_equal [1,2], subject.cmd_args
19
+ assert_equal 'opts', subject.opts['some']
20
+ end
21
+
22
+ should "complain about unknown cmds" do
23
+ runner = Dassets::Runner.new(['unknown'], {})
24
+ assert_raises(UnknownCmdError) { runner.run }
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,76 @@
1
+ require 'assert'
2
+ require 'dassets/server/request'
3
+
4
+ class Dassets::Server::Request
5
+
6
+ class BaseTests < Assert::Context
7
+ desc "Dassets::Server::Request"
8
+ setup do
9
+ Dassets.init
10
+ @req = file_request('GET', '/file1-daa05c683a4913b268653f7a7e36a5b4.txt')
11
+ end
12
+ teardown do
13
+ Dassets.reset
14
+ end
15
+ subject{ @req }
16
+
17
+ should have_imeths :for_asset_file?, :asset_path, :asset_file
18
+
19
+ should "know its asset_path" do
20
+ assert_equal 'file1.txt', subject.asset_path
21
+ end
22
+
23
+ should "know its asset_file" do
24
+ assert_equal Dassets['file1.txt'], subject.asset_file
25
+ end
26
+
27
+ should "know if it is for an asset file" do
28
+ # find nested path with matching md5
29
+ req = file_request('GET', '/nested/file3-d41d8cd98f00b204e9800998ecf8427e.txt')
30
+ assert req.for_asset_file?
31
+
32
+ # find not nested path with matching md5
33
+ req = file_request('HEAD', '/file1-daa05c683a4913b268653f7a7e36a5b4.txt')
34
+ assert req.for_asset_file?
35
+
36
+ # find even if md5 is *not* matching - just need to have any md5
37
+ req = file_request('GET', '/file1-d41d8cd98f00b204e9800998ecf8427e.txt')
38
+ assert req.for_asset_file?
39
+
40
+ # no find on invalid md5
41
+ req = file_request('GET', '/file1-daa05c683a4913b268653f7a7e36a.txt')
42
+ assert_not req.for_asset_file?
43
+
44
+ # no find on missing md5
45
+ req = file_request('HEAD', '/file1.txt')
46
+ assert_not req.for_asset_file?
47
+
48
+ # no find on unknown file
49
+ req = file_request('GET', '/some-file.txt')
50
+ assert_not req.for_asset_file?
51
+
52
+ # no find on unknown file with an md5
53
+ req = file_request('GET', '/some-file-daa05c683a4913b268653f7a7e36a5b4.txt')
54
+ assert_not req.for_asset_file?
55
+ end
56
+
57
+ should "return an asset path and an empty asset file if request not for asset file" do
58
+ req = file_request('GET', '/some-file.txt')
59
+
60
+ assert_equal '', req.asset_path
61
+ assert_equal Dassets::AssetFile.new('', ''), req.asset_file
62
+ end
63
+
64
+ protected
65
+
66
+ def file_request(method, path_info)
67
+ require 'dassets/server/request'
68
+ Dassets::Server::Request.new({
69
+ 'REQUEST_METHOD' => method,
70
+ 'PATH_INFO' => path_info
71
+ })
72
+ end
73
+
74
+ end
75
+
76
+ end