simple-websocket-vcr 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGES.md +16 -2
  4. data/README.md +0 -1
  5. data/lib/simple_websocket_vcr.rb +95 -76
  6. data/lib/simple_websocket_vcr/cassette.rb +115 -36
  7. data/lib/simple_websocket_vcr/configuration.rb +14 -11
  8. data/lib/simple_websocket_vcr/errors.rb +7 -9
  9. data/lib/simple_websocket_vcr/monkey_patch.rb +18 -0
  10. data/lib/simple_websocket_vcr/recordable_websocket_client.rb +97 -97
  11. data/lib/simple_websocket_vcr/version.rb +2 -4
  12. data/simple-websocket-vcr.gemspec +7 -6
  13. data/spec/fixtures/vcr_cassettes/EXPLICIT/some_explicitly_specified_json_cassette.json +30 -0
  14. data/spec/fixtures/vcr_cassettes/EXPLICIT/some_explicitly_specified_yaml_cassette.yml +44 -0
  15. data/spec/fixtures/vcr_cassettes/EXPLICIT/some_template.json +13 -0
  16. data/spec/fixtures/vcr_cassettes/EXPLICIT/some_template.yml +25 -0
  17. data/spec/fixtures/vcr_cassettes/VCR_for_WS/automatically_picked_cassette_name_is_ok,_when_describing_parent_and_example_child1.yml +2 -0
  18. data/spec/fixtures/vcr_cassettes/VCR_for_WS/automatically_picked_cassette_name_is_ok,_when_describing_parent_and_example_child2.yml +2 -0
  19. data/spec/fixtures/vcr_cassettes/VCR_for_WS/{automatically_picked_cassette_name_is_ok,_when_describing_parent_and_example_child1.json → automatically_picked_cassette_name_is_ok,_when_describing_parent_and_example_child2_for_json.json} +0 -0
  20. data/spec/fixtures/vcr_cassettes/VCR_for_WS/automatically_picked_cassette_name_is_ok,_when_using_context_foo_and_example_bar.yml +2 -0
  21. data/spec/fixtures/vcr_cassettes/VCR_for_WS/should_record_also_the_outgoing_communication.yml +11 -0
  22. data/spec/fixtures/vcr_cassettes/VCR_for_WS/{should_record_the_closing_event.json → should_record_the_closing_event(json).json} +1 -1
  23. data/spec/fixtures/vcr_cassettes/VCR_for_WS/should_record_the_closing_event(yaml).yml +6 -0
  24. data/spec/fixtures/vcr_cassettes/VCR_for_WS/should_record_the_very_first_message_caught_on_the_client_yielded_by_the_connect_method.yml +5 -0
  25. data/spec/vcr_spec.rb +160 -57
  26. metadata +62 -51
  27. data/spec/fixtures/vcr_cassettes/EXPLICIT/some_explicitly_specified_cassette.json +0 -30
  28. data/spec/fixtures/vcr_cassettes/VCR_for_WS/automatically_picked_cassette_name_is_ok,_when_describing_parent_and_example_child2.json +0 -3
  29. data/spec/fixtures/vcr_cassettes/VCR_for_WS/automatically_picked_cassette_name_is_ok,_when_using_context_foo_and_example_bar.json +0 -3
  30. data/spec/fixtures/vcr_cassettes/VCR_for_WS/should_record_also_the_outgoing_communication.json +0 -21
  31. data/spec/fixtures/vcr_cassettes/VCR_for_WS/should_record_the_very_first_message_caught_on_the_client_yielded_by_the_connect_method.json +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 78b408bc5fa868fa39eadc1396ff489392afde72
4
- data.tar.gz: 1efd70b10211a7b521670188ec85fbcedc6ff4fb
3
+ metadata.gz: 6fcc25d2e22bc79b1a5359917af80ec0f10ae45e
4
+ data.tar.gz: 2736b6895d6da6e42d372e478e92fbeb6bd7d14a
5
5
  SHA512:
6
- metadata.gz: 9fade33794fb192e477ede4ce59bf0d759faa5894ab4f84492bf188d22ebb0926d69976f0f27a4eddcbd86dbd7a5c10921238449ce55b25fbf28473187de4eb7
7
- data.tar.gz: 9062c5a2735114ab8e76e6a0f80f16184bf1d4280abf9f442a577d6119b47ec2f20e5aa510a750024b8c28d187a40d2413f0ddc3869e87bc471d0063ffdc7dd9
6
+ metadata.gz: 6f1e61a02a7c1796460d423406c2d870035f74b761939983292423329ccca8ace7715576b38b45f6b3e2b2ec673bace43bb1045bd12484f54b5159eafbdc11b0
7
+ data.tar.gz: f1a278cbb9e3dc882113e5a4e83bdf31f2a81af8fb20aaa2f72c17fd69c23d382ff00bf82eb301aae52a4f0eec4bc1c3482f3c9ba2a4339c373a9005607f2b29
data/.rubocop.yml CHANGED
@@ -24,3 +24,5 @@ ModuleLength:
24
24
  Enabled: false
25
25
  Style/ClassAndModuleChildren:
26
26
  Enabled: false
27
+ Style/ModuleFunction:
28
+ Enabled: false
data/CHANGES.md CHANGED
@@ -3,6 +3,20 @@
3
3
  This document describes the relevant changes between releases of the
4
4
  _simple-websocket-vcr_ project.
5
5
 
6
- ### V 0.0.1
6
+ ### 0.0.5 (not released yet)
7
+ * version is aligned with the VCR module so that coverall could do its job properly (https://travis-ci.org/Jiri-Kremser/simple-websocket-vcr/jobs/118142774#L215)
7
8
 
8
- * First release
9
+ ### 0.0.4
10
+ * if client waits for msg a therad is spawned and it checks the recording for consecutive read operations and translate it to the events, if write operation is on the top of the stack, the thread waits for the client to perform the write
11
+
12
+ ### 0.0.3
13
+ * making it more robust
14
+
15
+ ### 0.0.2
16
+ * bug fixes
17
+ * Rubocop
18
+ * actually, making it work with unseen scenarios
19
+
20
+ ### 0.0.1
21
+
22
+ * First release
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Simple websocket VCR
2
2
 
3
3
  [![Build Status](https://travis-ci.org/Jiri-Kremser/simple-websocket-vcr.png?branch=master)](https://travis-ci.org/Jiri-Kremser/simple-websocket-vcr)
4
-
5
4
  [![Coverage](https://coveralls.io/repos/github/Jiri-Kremser/simple-websocket-vcr/badge.svg?branch=master)](https://coveralls.io/github/Jiri-Kremser/simple-websocket-vcr?branch=master)
6
5
 
7
6
 
@@ -1,103 +1,122 @@
1
+ require 'json'
2
+ require 'yaml'
3
+ require 'websocket-client-simple'
1
4
  require 'simple_websocket_vcr/cassette'
2
5
  require 'simple_websocket_vcr/configuration'
3
6
  require 'simple_websocket_vcr/errors'
4
7
  require 'simple_websocket_vcr/recordable_websocket_client'
5
8
  require 'simple_websocket_vcr/version'
6
- require 'json'
7
- require 'websocket-client-simple'
9
+ require 'simple_websocket_vcr/monkey_patch'
10
+
11
+ module WebSocketVCR
12
+ extend self
13
+
14
+ # @return [String] the current version.
15
+ # @note This string also has singleton methods:
16
+ #
17
+ # * `major` [Integer] The major version.
18
+ # * `minor` [Integer] The minor version.
19
+ # * `patch` [Integer] The patch version.
20
+ # * `parts` [Array<Integer>] List of the version parts.
21
+ def version
22
+ @version ||= begin
23
+ string = WebSocketVCR::VERSION
24
+
25
+ def string.parts
26
+ split('.').map(&:to_i)
27
+ end
8
28
 
9
- module VCR
10
- module WebSocket
11
- def configure
12
- yield configuration
13
- end
29
+ def string.major
30
+ parts[0]
31
+ end
14
32
 
15
- def configuration
16
- @configuration ||= Configuration.new
17
- end
33
+ def string.minor
34
+ parts[1]
35
+ end
18
36
 
19
- def cassette
20
- @cassette
21
- end
37
+ def string.patch
38
+ parts[2]
39
+ end
22
40
 
23
- def cassette=(v)
24
- @cassette = v
41
+ string
25
42
  end
43
+ end
26
44
 
27
- def disabled
28
- @disabled || false
29
- end
45
+ def configure
46
+ yield configuration
47
+ end
30
48
 
31
- def disabled=(v)
32
- @disabled = v
33
- end
49
+ def configuration
50
+ @configuration ||= Configuration.new
51
+ end
34
52
 
35
- def save_session
36
- end
53
+ def cassette
54
+ @cassette
55
+ end
37
56
 
38
- def use_cassette(name, _options = {})
39
- fail ArgumentError, '`VCR.use_cassette` requires a block.' unless block_given?
40
- self.cassette = Cassette.new(name)
41
- yield
42
- cassette.save
43
- self.cassette = nil
44
- end
57
+ def cassette=(v)
58
+ @cassette = v
59
+ end
45
60
 
46
- def record(example, context, options = {}, &block)
47
- fail ArgumentError, '`VCR.record` requires a block.' unless block_given?
48
- name = filename_helper(example, context)
49
- use_cassette(name, options, &block)
50
- end
61
+ def disabled
62
+ @disabled || false
63
+ end
51
64
 
52
- def turn_off!(_options = {})
53
- # TODO: impl
54
- end
65
+ def disabled=(v)
66
+ @disabled = v
67
+ end
55
68
 
56
- def turned_on?
57
- !@cassette.nil?
58
- end
69
+ def save_session
70
+ end
59
71
 
60
- def turn_on!
61
- # TODO: impl
62
- end
72
+ # Use the specified cassette for either recording the real communication or replaying it during the tests.
73
+ # @param name [String] the cassette
74
+ # @param options [Hash] options for the cassette
75
+ # @option options [Symbol] :record if set to :none there will be no recording
76
+ # @option options [Symbol] :erb a sub-hash with variables used for ERB substitution in given cassette
77
+ def use_cassette(name, options = {})
78
+ fail ArgumentError, '`VCR.use_cassette` requires a block.' unless block_given?
79
+ self.cassette = Cassette.new(name, options)
80
+ yield
81
+ cassette.save
82
+ self.cassette = nil
83
+ end
63
84
 
64
- def live?
65
- @cassette && @cassette.recording?
66
- end
85
+ def record(example, context, options = {}, &block)
86
+ fail ArgumentError, '`VCR.record` requires a block.' unless block_given?
87
+ name = filename_helper(example, context)
88
+ use_cassette(name, options, &block)
89
+ end
67
90
 
68
- private
91
+ def turn_off!(_options = {})
92
+ # TODO: impl
93
+ end
69
94
 
70
- def filename_helper(example, context)
71
- if context.class.metadata[:parent_example_group].nil?
72
- example_name = example.description.gsub(/\s+/, '_')
73
- directory = context.class.metadata[:description].gsub(/\s+/, '_')
74
- else
75
- example_name = "#{context.class.metadata[:description]}_#{example.description}".gsub(/\s+/, '_')
76
- directory = context.class.metadata[:parent_example_group][:description].gsub(/\s+/, '_')
77
- end
78
- "#{directory}/#{example_name}.json"
79
- end
95
+ def turned_on?
96
+ !@cassette.nil?
97
+ end
80
98
 
81
- module_function :configure, :configuration, :cassette, :cassette=, :disabled, :disabled=, :save_session,
82
- :use_cassette, :record, :turn_off!, :turned_on?, :turn_on!, :filename_helper
99
+ def turn_on!
100
+ # TODO: impl
83
101
  end
84
- end
85
102
 
86
- module WebSocket::Client::Simple
87
- class << self
88
- alias_method :real_connect, :connect
89
-
90
- def connect(url, options = {})
91
- if VCR::WebSocket.configuration.hook_uris.any? { |u| url.include?(u) }
92
- cassette = VCR::WebSocket.cassette
93
- live = cassette.recording?
94
- real_client = real_connect(url, options) if live
95
- fake_client = VCR::WebSocket::RecordableWebsocketClient.new(cassette, live ? real_client : nil)
96
- yield fake_client if block_given?
97
- fake_client
98
- else
99
- real_connect(url, options)
100
- end
103
+ def live?
104
+ @cassette && @cassette.recording?
105
+ end
106
+
107
+ private
108
+
109
+ def filename_helper(example, context)
110
+ if context.class.metadata[:parent_example_group].nil?
111
+ example_name = example.description.gsub(/\s+/, '_')
112
+ directory = context.class.metadata[:description].gsub(/\s+/, '_')
113
+ else
114
+ example_name = "#{context.class.metadata[:description]}_#{example.description}".gsub(/\s+/, '_')
115
+ directory = context.class.metadata[:parent_example_group][:description].gsub(/\s+/, '_')
101
116
  end
117
+ "#{directory}/#{example_name}"
102
118
  end
119
+
120
+ module_function :configure, :configuration, :cassette, :cassette=, :disabled, :disabled=, :save_session,
121
+ :use_cassette, :record, :turn_off!, :turned_on?, :turn_on!, :filename_helper
103
122
  end
@@ -1,50 +1,129 @@
1
1
  require 'simple_websocket_vcr/errors'
2
2
  require 'fileutils'
3
3
 
4
- module VCR
5
- module WebSocket
6
- include Errors
7
-
8
- class Cassette
9
- attr_reader :name, :recording
10
-
11
- alias_method :recording?, :recording
12
-
13
- def initialize(name)
14
- @name = name
15
-
16
- if File.exist?(filename)
17
- @recording = false
18
- @contents = File.open(filename, &:read)
19
- @sessions = JSON.parse(@contents)
20
- else
21
- @recording = true
22
- @sessions = []
23
- end
4
+ module WebSocketVCR
5
+ include Errors
6
+
7
+ class Cassette
8
+ attr_reader :name, :options, :recording, :sessions
9
+
10
+ alias_method :recording?, :recording
11
+
12
+ def initialize(name, options)
13
+ @name = name
14
+ @options = options
15
+ @using_json = WebSocketVCR.configuration.json_cassettes
16
+ @name += @using_json ? '.json' : '.yml'
17
+
18
+ if File.exist?(filename)
19
+ @recording = false
20
+ @sessions = initialize_sessions filename
21
+ else
22
+ fail "No cassette '#{name}' found and recording has been turned off" if @options[:record] == :none
23
+ @recording = true
24
+ @sessions = []
24
25
  end
26
+ end
25
27
 
26
- def next_session
27
- if recording?
28
- @sessions << []
29
- @sessions.last
30
- else
31
- fail NoMoreSessionsError if @sessions.empty?
32
- @sessions.shift
33
- end
28
+ def next_session
29
+ if recording?
30
+ @sessions.push(@using_json ? RecordedJsonSession.new([]) : RecordedYamlSession.new([]))
31
+ @sessions.last
32
+ else
33
+ fail NoMoreSessionsError if @sessions.empty?
34
+ @sessions.shift
34
35
  end
36
+ end
35
37
 
36
- def save
37
- return unless recording?
38
- dirname = File.dirname(filename)
39
- FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
40
- File.open(filename, 'w') { |f| f.write(JSON.pretty_generate(@sessions)) }
38
+ def save
39
+ return unless recording?
40
+ dirname = File.dirname(filename)
41
+ # make sure the directory structure is there
42
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
43
+ if @using_json
44
+ text = JSON.pretty_generate(@sessions.map(&:record_entries))
45
+ else
46
+ text = { 'websocket_interactions' => @sessions.map(&:record_entries) }.to_yaml(Indent: 8)
41
47
  end
48
+ File.open(filename, 'w') { |f| f.write(text) }
49
+ end
50
+
51
+ protected
52
+
53
+ def filename
54
+ "#{WebSocketVCR.configuration.cassette_library_dir}/#{name}"
55
+ end
56
+
57
+ private
42
58
 
43
- protected
59
+ def initialize_sessions(filename)
60
+ file_content = File.open(filename, &:read)
44
61
 
45
- def filename
46
- "#{VCR::WebSocket.configuration.cassette_library_dir}/#{name}"
62
+ # do the ERB substitution
63
+ unless @options[:erb].nil?
64
+ require 'ostruct'
65
+ namespace = OpenStruct.new(@options[:erb])
66
+ file_content = ERB.new(file_content).result(namespace.instance_eval { binding })
47
67
  end
68
+
69
+ # parse JSON/YAML
70
+ if @using_json
71
+ parsed_content = JSON.parse(file_content)
72
+ sessions = RecordedJsonSession.load(parsed_content)
73
+ else
74
+ parsed_content = YAML.load(file_content)
75
+ sessions = RecordedYamlSession.load(parsed_content)
76
+ end
77
+ sessions
78
+ end
79
+ end
80
+
81
+ class RecordedSession
82
+ attr_reader :record_entries
83
+
84
+ def initialize(entries)
85
+ @record_entries = entries
86
+ end
87
+
88
+ def store(entry)
89
+ hash = entry.is_a?(RecordEntry) ? entry.attributes.map(&:to_s) : entry.map { |k, v| [k.to_s, v.to_s] }.to_h
90
+ @record_entries << hash
91
+ end
92
+
93
+ def next
94
+ RecordEntry.parse(@record_entries.shift)
95
+ end
96
+
97
+ def head
98
+ @record_entries.empty? ? nil : RecordEntry.parse(@record_entries.first)
99
+ end
100
+
101
+ def method_missing(method_name, *args, &block)
102
+ @record_entries.__send__(method_name, *args, &block)
103
+ end
104
+ end
105
+
106
+ class RecordedJsonSession < RecordedSession
107
+ def self.load(json)
108
+ json.map { |session| RecordedJsonSession.new(session) }
109
+ end
110
+ end
111
+
112
+ class RecordedYamlSession < RecordedSession
113
+ def self.load(yaml)
114
+ yaml['websocket_interactions'].map { |session| RecordedYamlSession.new(session) }
115
+ end
116
+ end
117
+
118
+ class RecordEntry
119
+ attr_accessor :operation, :type, :data
120
+
121
+ def self.parse(obj)
122
+ record_entry = RecordEntry.new
123
+ record_entry.operation = obj['operation']
124
+ record_entry.type = obj['type'] if obj['type']
125
+ record_entry.data = obj['data'] if obj['data']
126
+ record_entry
48
127
  end
49
128
  end
50
129
  end
@@ -1,16 +1,19 @@
1
- module VCR
2
- module WebSocket
3
- class Configuration
4
- attr_accessor :cassette_library_dir, :hook_uris
1
+ module WebSocketVCR
2
+ class Configuration
3
+ attr_accessor :cassette_library_dir, :hook_uris, :json_cassettes
5
4
 
6
- def initialize
7
- reset_defaults!
8
- end
5
+ def initialize
6
+ reset_defaults!
7
+ end
8
+
9
+ def reset_defaults!
10
+ @cassette_library_dir = 'spec/fixtures/vcr_cassettes'
11
+ @hook_uris = []
12
+ @json_cassettes = false
13
+ end
9
14
 
10
- def reset_defaults!
11
- @cassette_library_dir = 'spec/fixtures/vcr_cassettes'
12
- @hook_uris = []
13
- end
15
+ def method_missing(method_name, *_args, &_block)
16
+ puts 'unknown method: ' + method_name.to_s
14
17
  end
15
18
  end
16
19
  end