dassets 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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