diversion 0.0.1

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 (83) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +193 -0
  6. data/Rakefile +7 -0
  7. data/diversion.gemspec +40 -0
  8. data/lib/diversion.rb +35 -0
  9. data/lib/diversion/client.rb +21 -0
  10. data/lib/diversion/configurable.rb +100 -0
  11. data/lib/diversion/decode.rb +21 -0
  12. data/lib/diversion/decode/json.rb +47 -0
  13. data/lib/diversion/decode/params.rb +55 -0
  14. data/lib/diversion/encode.rb +67 -0
  15. data/lib/diversion/encode/json.rb +31 -0
  16. data/lib/diversion/encode/params.rb +26 -0
  17. data/lib/diversion/error.rb +2 -0
  18. data/lib/diversion/error/bad_url_data_format.rb +6 -0
  19. data/lib/diversion/error/configuration_error.rb +6 -0
  20. data/lib/diversion/error/key_missing_error.rb +6 -0
  21. data/lib/diversion/error/uri_missing_error.rb +6 -0
  22. data/lib/diversion/mailer.rb +11 -0
  23. data/lib/diversion/signing.rb +19 -0
  24. data/lib/diversion/url.rb +43 -0
  25. data/lib/diversion/version.rb +17 -0
  26. data/spec/coverage/assets/0.7.1/application.css +1110 -0
  27. data/spec/coverage/assets/0.7.1/application.js +626 -0
  28. data/spec/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  29. data/spec/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  30. data/spec/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  31. data/spec/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  32. data/spec/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  33. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  34. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  35. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  36. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  37. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  38. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  39. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  40. data/spec/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  41. data/spec/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  42. data/spec/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  43. data/spec/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  44. data/spec/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  45. data/spec/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  46. data/spec/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  47. data/spec/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  48. data/spec/coverage/assets/0.7.1/favicon_green.png +0 -0
  49. data/spec/coverage/assets/0.7.1/favicon_red.png +0 -0
  50. data/spec/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  51. data/spec/coverage/assets/0.7.1/loading.gif +0 -0
  52. data/spec/coverage/assets/0.7.1/magnify.png +0 -0
  53. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  54. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  55. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  56. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  57. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  58. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  59. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  60. data/spec/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  61. data/spec/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  62. data/spec/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  63. data/spec/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  64. data/spec/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  65. data/spec/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  66. data/spec/coverage/index.html +3146 -0
  67. data/spec/diversion/client_spec.rb +23 -0
  68. data/spec/diversion/configurable_spec.rb +128 -0
  69. data/spec/diversion/decode/json_decode_spec.rb +70 -0
  70. data/spec/diversion/decode/params_decode_spec.rb +74 -0
  71. data/spec/diversion/decode_spec.rb +12 -0
  72. data/spec/diversion/encode/json_encode_spec.rb +39 -0
  73. data/spec/diversion/encode/params_encode_spec.rb +39 -0
  74. data/spec/diversion/encode_spec.rb +30 -0
  75. data/spec/diversion/mailer_spec.rb +31 -0
  76. data/spec/diversion/support/global_shared_context.rb +29 -0
  77. data/spec/diversion/url_spec.rb +36 -0
  78. data/spec/fixtures/mail.html +13 -0
  79. data/spec/fixtures/sample_email.multipart +39 -0
  80. data/spec/fixtures/sample_email.text +12 -0
  81. data/spec/spec_helper.rb +84 -0
  82. metadata +323 -0
  83. metadata.gz.sig +0 -0
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Client do
4
+
5
+ it "delegates to a client" do
6
+ expect(Diversion.encode(HTML)).to eq(html_params_encoded)
7
+ end
8
+
9
+ it "configures" do
10
+ client = Diversion::Client.new
11
+ client.configure do |c|
12
+ c.host = "dummy.host"
13
+ end
14
+ expect(client.host).to eq("dummy.host")
15
+ end
16
+
17
+ it "allows for separate configs" do
18
+ m1 = Diversion::Client.new({:host => "test1"})
19
+ m2 = Diversion::Client.new({:host => "test2"})
20
+ expect(m1.host).to_not eq(m2.host)
21
+ end
22
+
23
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ class ConfigTestStub
4
+ include Diversion::Configurable
5
+ end
6
+
7
+ describe Diversion::Configurable do
8
+
9
+ let(:config) { config = ConfigTestStub.new; config.reset!; config }
10
+
11
+ def set_config(&block) config.configure do |c| c.instance_eval(&block) end; end
12
+ def host(value); set_config{|c|c.host = value}; end
13
+ def path(value); set_config{|c|c.path = value}; end
14
+ def port(value); set_config{|c|c.port = value}; end
15
+ def encode_uris(value); set_config{|c|c.encode_uris = value}; end
16
+ def sign_length(value); set_config{|c|c.sign_length = value}; end
17
+ def url_encoding(value); set_config{|c|c.url_encoding = value}; end
18
+ def url_decoding(value); set_config{|c|c.url_decoding = value}; end
19
+
20
+ describe "host" do
21
+ it "must be a string" do
22
+ expect{host(1)}.to raise_error
23
+ end
24
+ it "accepts string type" do
25
+ expect{host('test')}.to_not raise_error
26
+ end
27
+ it "cannot end with trailing slash" do
28
+ expect{host('test/')}.to raise_error
29
+ end
30
+ it "doesnt accept nil" do
31
+ expect{host(nil)}.to raise_error
32
+ end
33
+ end
34
+
35
+ describe "path" do
36
+ it "must be a string" do
37
+ expect{path(1)}.to raise_error
38
+ end
39
+ it "accepts string type" do
40
+ expect{path('test/')}.to_not raise_error
41
+ end
42
+ it "must end with trailing slash" do
43
+ expect{path('test')}.to raise_error
44
+ end
45
+ it "doesnt accept nil" do
46
+ expect{path(nil)}.to raise_error
47
+ end
48
+ end
49
+
50
+ describe "port" do
51
+ it "must be an integer" do
52
+ expect{port('test')}.to raise_error
53
+ end
54
+ it "accepts an integer" do
55
+ expect{port(1)}.to_not raise_error
56
+ end
57
+ it "does not accept 0" do
58
+ expect{port(0)}.to raise_error
59
+ end
60
+ it "does not accept <0" do
61
+ expect{port(-1)}.to raise_error
62
+ end
63
+ it "doesnt accept nil" do
64
+ expect{port(nil)}.to raise_error
65
+ end
66
+ end
67
+
68
+ describe "sign_length" do
69
+ it "must be an integer" do
70
+ expect{sign_length('test')}.to raise_error
71
+ end
72
+ it "accepts 0 (disaled)" do
73
+ expect{sign_length(0)}.to_not raise_error
74
+ end
75
+ it "accepts integer at lower range" do
76
+ expect{sign_length(1)}.to_not raise_error
77
+ end
78
+ it "accepts integer at higher range" do
79
+ expect{sign_length(Diversion::Signing::MAX_SIGN_LENGTH)}.to_not raise_error
80
+ end
81
+ it "does not accept <0" do
82
+ expect{sign_length(-1)}.to raise_error
83
+ end
84
+ it "doesnt accept nil" do
85
+ expect{sign_length(nil)}.to raise_error
86
+ end
87
+ end
88
+
89
+ describe "encode_uris" do
90
+ it "must be an array" do
91
+ expect{encode_uris('test')}.to raise_error
92
+ end
93
+ it "accepts uri" do
94
+ expect{encode_uris(['http'])}.to_not raise_error
95
+ end
96
+ it "doesnt accept nil" do
97
+ expect{encode_uris(nil)}.to raise_error
98
+ end
99
+ it "doesnt accept empty array" do
100
+ expect{encode_uris([])}.to raise_error
101
+ end
102
+ end
103
+
104
+ describe "url_encoding" do
105
+ it "must be a module" do
106
+ expect{url_encoding('test')}.to raise_error
107
+ end
108
+ it "accepts a valid module" do
109
+ expect{url_encoding(Diversion::Encode::Json)}.to_not raise_error
110
+ end
111
+ it "doesn't accept an invalid module" do
112
+ expect{url_encoding(Diversion::Decode::Json)}.to raise_error
113
+ end
114
+ end
115
+
116
+ describe "url_decoding" do
117
+ it "must be a module" do
118
+ expect{url_decoding('test')}.to raise_error
119
+ end
120
+ it "accepts a valid module" do
121
+ expect{url_decoding(Diversion::Decode::Json)}.to_not raise_error
122
+ end
123
+ it "doesn't accept an invalid module" do
124
+ expect{url_decoding(Diversion::Encode::Json)}.to raise_error
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Decode::Json do
4
+
5
+ include_context "json"
6
+
7
+ it "returns correct type for non-signed" do
8
+ expect(decode_json).to be_a Hash
9
+ end
10
+
11
+ it "returns correct type for signed" do
12
+ expect(decode_json_signed).to be_a Hash
13
+ end
14
+
15
+ it "raises when data format invalid" do
16
+ expect{client.decode("test-test-test")}.to raise_error(Diversion::Error::BadUrlDataFormat)
17
+ end
18
+
19
+ it "doesn't parse when passed bad data" do
20
+ expect(client.decode("test-test")[:parsed]).to be_false
21
+ end
22
+
23
+ it "parses when passed good data" do
24
+ expect(decode_json[:parsed]).to be_true
25
+ end
26
+
27
+ it "returns expected values when passed good unsigned data" do
28
+ hash = decode_json
29
+ expect(hash[:url]).to eq("http://test.com/test")
30
+ expect(hash[:signed]).to be_false
31
+ expect(hash[:key_presented]).to be_empty
32
+ expect(hash[:key_expected]).to be_empty
33
+ expect(hash[:key_verified]).to be_false
34
+ end
35
+
36
+ it "returns expected values when passed good signed data" do
37
+ hash = decode_json_signed
38
+ expect(hash[:url]).to eq("http://test.com/test")
39
+ expect(hash[:signed]).to be_true
40
+ expect(hash[:key_presented]).to eq(JSON_KEY)
41
+ expect(hash[:key_expected]).to eq(JSON_KEY)
42
+ expect(hash[:key_verified]).to be_true
43
+ end
44
+
45
+ it "returns expected values when passed badly signed data" do
46
+ hash = decode_json_bad_key
47
+ expect(hash[:url]).to eq("http://test.com/test")
48
+ expect(hash[:signed]).to be_true
49
+ expect(hash[:key_presented]).to eq(JSON_KEY_BAD)
50
+ expect(hash[:key_expected]).to eq(JSON_KEY)
51
+ expect(hash[:key_verified]).to be_false
52
+ end
53
+
54
+ it "decodes parameters as expected" do
55
+ enc = client.encode(HTML, {:b => 999})
56
+ data = enc.scan(/".*?\/.*?\/.*?\/.*?\/.*?\/(.*?)"/).first.first
57
+ result = client.decode(data)
58
+ expect(result[:test]).to eq("1234")
59
+ expect(result[:b]).to eq(999)
60
+ end
61
+
62
+ it "decodes parameters as expected when signed" do
63
+ enc = client_sign.encode(HTML, {:b => 999})
64
+ data = enc.scan(/".*?\/.*?\/.*?\/.*?\/.*?\/(.*?)"/).first.first
65
+ hash = client.decode(data)
66
+ expect(hash[:test]).to eq("1234")
67
+ expect(hash[:b]).to eq(999)
68
+ end
69
+
70
+ end
@@ -0,0 +1,74 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Decode::Params do
4
+
5
+ include_context "params"
6
+
7
+ it "returns correct type for non-signed" do
8
+ expect(decode_params).to be_a Hash
9
+ end
10
+
11
+ it "returns correct type for signed" do
12
+ expect(decode_params_signed).to be_a Hash
13
+ end
14
+
15
+ it "raises when data format invalid" do
16
+ expect{client.decode("badparam=badvalue")}.to raise_error(Diversion::Error::BadUrlDataFormat)
17
+ end
18
+
19
+ it "raises when data format invalid" do
20
+ expect{client.decode("rubbish")}.to raise_error(Diversion::Error::BadUrlDataFormat)
21
+ end
22
+
23
+ it "raises when missing url data parameter" do
24
+ expect{client.decode("d=#{CGI::escape('badparam=badvalue')}")}.to raise_error(Diversion::Error::BadUrlDataFormat)
25
+ end
26
+
27
+ it "raises when missing url data parameter empty" do
28
+ expect{client.decode("d=#{CGI::escape('url=')}")}.to raise_error(Diversion::Error::BadUrlDataFormat)
29
+ end
30
+
31
+ it "returns expected values when passed good unsigned data" do
32
+ hash = decode_params
33
+ expect(hash[:url]).to eq("http://test.com/test")
34
+ expect(hash[:signed]).to be_false
35
+ expect(hash[:key_presented]).to be_empty
36
+ expect(hash[:key_expected]).to be_empty
37
+ expect(hash[:key_verified]).to be_false
38
+ end
39
+
40
+ it "returns expected values when passed good signed data" do
41
+ hash = decode_params_signed
42
+ expect(hash[:url]).to eq("http://test.com/test")
43
+ expect(hash[:signed]).to be_true
44
+ expect(hash[:key_presented]).to eq(PARAMS_KEY)
45
+ expect(hash[:key_expected]).to eq(PARAMS_KEY)
46
+ expect(hash[:key_verified]).to be_true
47
+ end
48
+
49
+ it "returns expected values when passed badly signed data" do
50
+ hash = decode_params_bad_key
51
+ expect(hash[:url]).to eq("http://test.com/test")
52
+ expect(hash[:signed]).to be_true
53
+ expect(hash[:key_presented]).to eq(PARAMS_KEY_BAD)
54
+ expect(hash[:key_expected]).to eq(PARAMS_KEY)
55
+ expect(hash[:key_verified]).to be_false
56
+ end
57
+
58
+ it "decodes parameters as expected" do
59
+ enc = client.encode(HTML, {:b => 999})
60
+ data = enc.scan(/".*?\/.*?\/.*?\/.*?\/.*?\/(.*?)"/).first.first[1..-1]
61
+ result = client.decode(data)
62
+ expect(result[:test]).to eq("1234")
63
+ expect(result[:b]).to eq("999")
64
+ end
65
+
66
+ it "decodes parameters as expected when signed" do
67
+ enc = client_sign.encode(HTML, {:b => 999})
68
+ data = enc.scan(/".*?\/.*?\/.*?\/.*?\/.*?\/(.*?)"/).first.first[1..-1]
69
+ hash = client.decode(data)
70
+ expect(hash[:test]).to eq("1234")
71
+ expect(hash[:b]).to eq("999")
72
+ end
73
+
74
+ end
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Decode do
4
+
5
+ # we use json encoding here but it doesn't matter as we're testing common functionality
6
+ include_context "json"
7
+
8
+ it "raise ArgumentError if data.length == 0" do
9
+ expect{client.decode("")}.to raise_error(ArgumentError)
10
+ end
11
+
12
+ end
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Encode::Json do
4
+
5
+ include_context "json"
6
+
7
+ it "returns correct type" do
8
+ expect(encode_email).to be_a String
9
+ end
10
+
11
+ it "uses correct path" do
12
+ expect(encode_json_html).to match(/http:\/\/localhost.domain\/redirect\/1\/[A-Za-z0-9].*\">test<\/a>/)
13
+ end
14
+
15
+ it "doesn't add port for 80" do
16
+ client.port = 80
17
+ expect(encode_json_html).to_not match(/http:\/\/localhost.domain:80\/redirect/)
18
+ end
19
+
20
+ it "adds port number for non-80 port" do
21
+ client.port = 81
22
+ expect(encode_json_html).to match(/http:\/\/localhost.domain:81\/redirect/)
23
+ end
24
+
25
+ it "doesn't sign by default" do
26
+ expect(encode_json_html).to_not match(/-/)
27
+ end
28
+
29
+ it "signs correctly" do
30
+ client_sign.sign_length = 32
31
+ expect(encode_json_html_signed).to match(/-[A-Za-z0-9]{32}\"/)
32
+ end
33
+
34
+ it "observes sign_length" do
35
+ client_sign.sign_length = 2
36
+ expect(encode_json_html_signed).to match(/-[A-Za-z0-9]{2}\"/)
37
+ end
38
+
39
+ end
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Encode::Params do
4
+
5
+ include_context "params"
6
+
7
+ it "returns correct type" do
8
+ expect(encode_email).to be_a String
9
+ end
10
+
11
+ it "uses correct path" do
12
+ expect(encode_params_html).to eq(html_params_encoded)
13
+ end
14
+
15
+ it "doesn't add port for 80" do
16
+ client.port = 80
17
+ expect(encode_params_html).to eq(html_params_encoded)
18
+ end
19
+
20
+ it "adds port number for non-80 port" do
21
+ client.port = 81
22
+ expect(encode_params_html).to eq(html_params_encoded({:port => 81}))
23
+ end
24
+
25
+ it "doesn't sign by default" do
26
+ expect(encode_params_html).to eq(html_params_encoded)
27
+ end
28
+
29
+ it "signs correctly" do
30
+ client_sign.sign_length = 32
31
+ expect(encode_params_html_signed).to eq(html_params_encoded({:sign_length => DEFAULT_SIGN_LEN}))
32
+ end
33
+
34
+ it "observes sign_length" do
35
+ client_sign.sign_length = 2
36
+ expect(encode_params_html_signed).to eq(html_params_encoded({:sign_length => 2}))
37
+ end
38
+
39
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe Diversion::Encode do
4
+
5
+ # we use json encoding here but it doesn't matter as we're testing common functionality
6
+ include_context "json"
7
+
8
+ it "ignores non-http uris" do
9
+ expect(encode_email).to include('mailto:jess@doesnotexist.domain')
10
+ end
11
+
12
+ it "converts http uris" do
13
+ expect(encode_email).to_not include('https://twitter.com/intent/tweet?in_reply_to=51113028241989632')
14
+ end
15
+
16
+ it "removes data attributes" do
17
+ expect(encode_email).to_not include('data-')
18
+ end
19
+
20
+ it "raises when signing key not set" do
21
+ client_sign.sign_key = nil
22
+ expect { encode_json_html_signed }.to raise_error(Diversion::Error::KeyMissingError)
23
+ end
24
+
25
+ it "raises when no uris defined" do
26
+ client.encode_uris = []
27
+ expect { encode_json_html_signed }.to raise_error(Diversion::Error::ConfigurationError)
28
+ end
29
+
30
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mail::Message do
4
+
5
+ let(:multipart) { Mail::Message.new(fixture('sample_email.multipart').read).diversion }
6
+ let(:multipart_args) { Mail::Message.new(fixture('sample_email.multipart').read).diversion({:a => 1}, {:encode_uris => ['http']}) }
7
+ let(:text_only) { Mail::Message.new(fixture('sample_email.text').read).diversion }
8
+
9
+ it "replaces html part" do
10
+ expect(multipart.html_part.body).to include('<a href="http://localhost.domain/redirect/1/?d=url%3Dhttp%253A%252F%252Fwww.youtube.com%252Fwatch%253Fv%253DFE_9CzLCbkY">Dream of the 90s</a>')
11
+ expect(multipart.html_part.body).to include('<a href="http://localhost.domain/redirect/1/?d=url%3Dhttps%253A%252F%252Fwww.youtube.com%252Fwatch%253Fv%253DOyQ6pqPFwTI">Customers only</a>')
12
+ end
13
+
14
+ it "doesn't replace text part" do
15
+ expect(text_only.body).to include('http://www.youtube.com/watch?v=FE_9CzLCbkY')
16
+ expect(text_only.body).to include('http://www.youtube.com/watch?v=OyQ6pqPFwTI')
17
+ end
18
+
19
+ it "doesn't fail when only text part present" do
20
+ expect{text_only}.to_not raise_error
21
+ end
22
+
23
+ it "honors additional global data parameters" do
24
+ expect(multipart_args.html_part.body).to include('<a href="http://localhost.domain/redirect/1/?d=a%3D1%26url%3Dhttp%253A%252F%252Fwww.youtube.com%252Fwatch%253Fv%253DFE_9CzLCbkY">Dream of the 90s</a>')
25
+ end
26
+
27
+ it "honors additional config arguments" do
28
+ expect(multipart_args.html_part.body).to include('<a href="https://www.youtube.com/watch?v=OyQ6pqPFwTI">Customers only</a>')
29
+ end
30
+
31
+ end