couch_potato-rspec 4.0.1 → 4.0.2
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.
- checksums.yaml +4 -4
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
- data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +204 -0
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +63 -0
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
- data/lib/couch_potato/rspec/matchers.rb +30 -0
- data/lib/couch_potato/rspec/stub_db.rb +66 -0
- data/lib/couch_potato/rspec.rb +2 -0
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9187c364f8b090f31efbda76468aca05fcf4e5082349419c360c6e497d0f945f
|
4
|
+
data.tar.gz: d34ff1403b51771aa0b2718cf2e4558d026397217a708ba740d279a7df9de290
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b5c131ae6fc00d081fc2ff4cefbe6200b9eb8f9a0fec9ffd82421b5bf11addfe5484ff263319d8a5290b9d7494c5ea110b401f4fd493e49c29c47ddcabd4947
|
7
|
+
data.tar.gz: 2d46845862c4a4334b0eb162e63dc1be41046647edd33ecb53efd95d799516bfea6b14bbe9d693b38288f8e95b46000777e55af37ff09d697159bca9d026ecd6
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module RSpec
|
3
|
+
class ListAsProxy
|
4
|
+
def initialize(results_ruby)
|
5
|
+
@results_ruby = results_ruby
|
6
|
+
end
|
7
|
+
|
8
|
+
def as(expected_ruby)
|
9
|
+
ListAsMatcher.new(expected_ruby, @results_ruby)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ListAsMatcher
|
14
|
+
include ::RSpec::Matchers::Composable
|
15
|
+
|
16
|
+
def initialize(expected_ruby, results_ruby)
|
17
|
+
@expected_ruby = expected_ruby
|
18
|
+
@results_ruby = results_ruby
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?(view_spec)
|
22
|
+
js = <<-JS
|
23
|
+
(function() {
|
24
|
+
var results = #{@results_ruby.to_json};
|
25
|
+
var listed = '';
|
26
|
+
var list = #{view_spec.list_function};
|
27
|
+
|
28
|
+
var getRow = function() {
|
29
|
+
return results.rows.shift();
|
30
|
+
};
|
31
|
+
var send = function(text) {
|
32
|
+
listed = listed + text;
|
33
|
+
};
|
34
|
+
list();
|
35
|
+
return JSON.stringify(JSON.parse(listed));
|
36
|
+
})()
|
37
|
+
|
38
|
+
JS
|
39
|
+
@actual_ruby = JSON.parse(ExecJS.eval(js))
|
40
|
+
|
41
|
+
values_match? @expected_ruby, @actual_ruby
|
42
|
+
end
|
43
|
+
|
44
|
+
def failure_message
|
45
|
+
"Expected to list as #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
|
46
|
+
end
|
47
|
+
|
48
|
+
def failure_message_when_negated
|
49
|
+
"Expected to not list as #{@expected_ruby.inspect} but did."
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module CouchPotato
|
4
|
+
module RSpec
|
5
|
+
class MapReduceToProxy
|
6
|
+
def initialize(*input_ruby)
|
7
|
+
@input_ruby, @options = input_ruby.flatten, {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_options(options)
|
11
|
+
@options = options
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def to(*expected_ruby)
|
16
|
+
MapReduceToMatcher.new(expected_ruby, @input_ruby, @options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class MapReduceToMatcher
|
21
|
+
include ::RSpec::Matchers::Composable
|
22
|
+
|
23
|
+
def initialize(expected_ruby, input_ruby, options)
|
24
|
+
@expected_ruby = expected_ruby
|
25
|
+
@input_ruby = input_ruby
|
26
|
+
@options = options
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?(view_spec)
|
30
|
+
js = <<-JS
|
31
|
+
(function() {
|
32
|
+
var sum = function(values) {
|
33
|
+
return values.reduce(function(memo, value) { return memo + value; });
|
34
|
+
};
|
35
|
+
// Equivalents of couchdb built-in reduce functions whose names can be
|
36
|
+
// given as the reduce function in the view_spec:
|
37
|
+
var _sum = function(keys, values, rereduce) {
|
38
|
+
return sum(values);
|
39
|
+
}
|
40
|
+
var _count = function(keys, values, rereduce) {
|
41
|
+
if (rereduce) {
|
42
|
+
return sum(values);
|
43
|
+
} else {
|
44
|
+
return values.length;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
var _stats = function(keys, values, rereduce) {
|
48
|
+
var result = {sum: 0, count: 0, min: Number.MAX_VALUE, max: Number.MIN_VALUE, sumsqr: 0};
|
49
|
+
if (rereduce) {
|
50
|
+
for (var i in values) {
|
51
|
+
var value = values[i];
|
52
|
+
result.sum += value.sum;
|
53
|
+
result.count += value.count;
|
54
|
+
result.min = Math.min(result.min, value.min);
|
55
|
+
result.max = Math.max(result.max, value.max);
|
56
|
+
result.sumsqr += value.sumsqr;
|
57
|
+
}
|
58
|
+
} else {
|
59
|
+
for (var i in values) {
|
60
|
+
var value = values[i];
|
61
|
+
result.sum += value;
|
62
|
+
result.count += 1;
|
63
|
+
result.min = Math.min(result.min, value);
|
64
|
+
result.max = Math.max(result.max, value);
|
65
|
+
result.sumsqr += Math.pow(value, 2);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
return result;
|
69
|
+
}
|
70
|
+
|
71
|
+
var docs = #{@input_ruby.to_json};
|
72
|
+
var options = #{@options.to_json};
|
73
|
+
var map = #{view_spec.map_function};
|
74
|
+
var reduce = #{view_spec.reduce_function};
|
75
|
+
var lib = #{view_spec.respond_to?(:lib) && view_spec.lib.to_json};
|
76
|
+
var collate = (function() { var module = {exports: {}}; var exports = module.exports; eval(#{File.read(File.expand_path(File.dirname(__FILE__) + '/../../../../vendor/pouchdb-collate/pouchdb-collate.js')).to_json}); return module.exports.collate;})();
|
77
|
+
|
78
|
+
// Map the input docs
|
79
|
+
var require = function(modulePath) {
|
80
|
+
var module = {exports: {}};
|
81
|
+
var exports = module.exports;
|
82
|
+
var pathArray = modulePath.split("/").slice(2);
|
83
|
+
var result = lib;
|
84
|
+
for (var i in pathArray) {
|
85
|
+
result = result[pathArray[i]];
|
86
|
+
}
|
87
|
+
eval(result);
|
88
|
+
return module.exports;
|
89
|
+
}
|
90
|
+
|
91
|
+
var unfilteredMapResults = [];
|
92
|
+
var emit = function(key, value) {
|
93
|
+
unfilteredMapResults.push({key: key, value: value});
|
94
|
+
};
|
95
|
+
for (var i in docs) {
|
96
|
+
map(docs[i]);
|
97
|
+
}
|
98
|
+
|
99
|
+
// Filter results by key/keys/startkey/endkey (if given).
|
100
|
+
var mapResults = [];
|
101
|
+
if (options.key) {
|
102
|
+
for (var r in unfilteredMapResults) {
|
103
|
+
var result = unfilteredMapResults[r];
|
104
|
+
if (collate(result.key, options.key) == 0)
|
105
|
+
mapResults.push(result);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
// couchdb does not support multiple keys for reduce views without group=true
|
109
|
+
else if (options.keys && options.group) {
|
110
|
+
for (var r in unfilteredMapResults) {
|
111
|
+
var result = unfilteredMapResults[r];
|
112
|
+
for (var k in options.keys) {
|
113
|
+
var key = options.keys[k];
|
114
|
+
if (collate(result.key, key) == 0) {
|
115
|
+
mapResults.push(result);
|
116
|
+
break;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
else if (options.startkey || options.endkey) {
|
122
|
+
for (var r in unfilteredMapResults) {
|
123
|
+
var result = unfilteredMapResults[r];
|
124
|
+
if (options.startkey && collate(options.startkey, result.key) > 0)
|
125
|
+
continue;
|
126
|
+
if (options.endkey && collate(result.key, options.endkey) > 0)
|
127
|
+
continue;
|
128
|
+
mapResults.push(result);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
else {
|
132
|
+
mapResults = unfilteredMapResults;
|
133
|
+
}
|
134
|
+
|
135
|
+
// Group the map results, honoring the group and group_level options
|
136
|
+
var grouped = [];
|
137
|
+
if (options.group || options.group_level) {
|
138
|
+
var groupLevel = options.group_level;
|
139
|
+
if (groupLevel == "exact" || options.group == true)
|
140
|
+
groupLevel = 9999;
|
141
|
+
|
142
|
+
for (var mr in mapResults) {
|
143
|
+
var mapResult = mapResults[mr];
|
144
|
+
var groupedKey = Array.isArray(mapResult.key) ? mapResult.key.slice(0, groupLevel) : mapResult.key;
|
145
|
+
var groupFound = false;
|
146
|
+
for (var g in grouped) {
|
147
|
+
var group = grouped[g];
|
148
|
+
if (collate(groupedKey, group.groupedKey) == 0) {
|
149
|
+
group.keys.push(mapResult.key);
|
150
|
+
group.values.push(mapResult.value);
|
151
|
+
groupFound = true;
|
152
|
+
break;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
if (!groupFound)
|
157
|
+
grouped.push({keys: [mapResult.key], groupedKey: groupedKey, values: [mapResult.value]});
|
158
|
+
}
|
159
|
+
} else {
|
160
|
+
var group = {keys: null, groupedKey: null, values: []};
|
161
|
+
for (var mr in mapResults)
|
162
|
+
group.values.push(mapResults[mr].value);
|
163
|
+
grouped.push(group);
|
164
|
+
}
|
165
|
+
|
166
|
+
// Reduce the grouped map results
|
167
|
+
var results = [];
|
168
|
+
for (var g in grouped) {
|
169
|
+
var group = grouped[g], reduced = null;
|
170
|
+
if (group.values.length >= 2) {
|
171
|
+
// Split the values into two parts, reduce each part, then rereduce those results
|
172
|
+
var mid = parseInt(group.values.length / 2);
|
173
|
+
var keys1 = (group.keys || []).slice(0, mid),
|
174
|
+
values1 = group.values.slice(0, mid);
|
175
|
+
var reduced1 = reduce(keys1, values1, false);
|
176
|
+
var keys2 = (group.keys || []).slice(mid, group.values.length),
|
177
|
+
values2 = group.values.slice(mid, group.values.length);
|
178
|
+
var reduced2 = reduce(keys2, values2, false);
|
179
|
+
reduced = reduce(null, [reduced1, reduced2], true);
|
180
|
+
} else {
|
181
|
+
// Not enough values to split, so just reduce, and then rereduce the single result
|
182
|
+
reduced = reduce(group.keys, group.values, false);
|
183
|
+
reduced = reduce(null, [reduced], true);
|
184
|
+
}
|
185
|
+
results.push({key: group.groupedKey, value: reduced});
|
186
|
+
}
|
187
|
+
|
188
|
+
return JSON.stringify(results);
|
189
|
+
})()
|
190
|
+
JS
|
191
|
+
@actual_ruby = JSON.parse(ExecJS.eval(js))
|
192
|
+
values_match? @expected_ruby, @actual_ruby
|
193
|
+
end
|
194
|
+
|
195
|
+
def failure_message
|
196
|
+
"Expected to map/reduce to #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
|
197
|
+
end
|
198
|
+
|
199
|
+
def failure_message_when_negated
|
200
|
+
"Expected not to map/reduce to #{@actual_ruby.inspect} but did."
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
|
4
|
+
module CouchPotato
|
5
|
+
module RSpec
|
6
|
+
class MapToProxy
|
7
|
+
def initialize(input_ruby)
|
8
|
+
@input_ruby = input_ruby
|
9
|
+
end
|
10
|
+
|
11
|
+
def to(*expected_ruby)
|
12
|
+
MapToMatcher.new(expected_ruby, @input_ruby)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MapToMatcher
|
17
|
+
include ::RSpec::Matchers::Composable
|
18
|
+
|
19
|
+
def initialize(expected_ruby, input_ruby)
|
20
|
+
@expected_ruby = expected_ruby
|
21
|
+
@input_ruby = input_ruby
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches?(view_spec)
|
25
|
+
js = <<-JS
|
26
|
+
(function() {
|
27
|
+
var doc = #{@input_ruby.to_json};
|
28
|
+
var map = #{view_spec.map_function};
|
29
|
+
var lib = #{view_spec.respond_to?(:lib) && view_spec.lib.to_json};
|
30
|
+
var result = [];
|
31
|
+
var require = function(modulePath) {
|
32
|
+
var module = {exports: {}};
|
33
|
+
var exports = module.exports;
|
34
|
+
var pathArray = modulePath.split("/").slice(2);
|
35
|
+
var result = lib;
|
36
|
+
for (var i in pathArray) {
|
37
|
+
result = result[pathArray[i]];
|
38
|
+
}
|
39
|
+
eval(result);
|
40
|
+
return module.exports;
|
41
|
+
}
|
42
|
+
|
43
|
+
var emit = function(key, value) {
|
44
|
+
result.push([key, value]);
|
45
|
+
};
|
46
|
+
map(doc);
|
47
|
+
return JSON.stringify(result);
|
48
|
+
})()
|
49
|
+
JS
|
50
|
+
@actual_ruby = JSON.parse(ExecJS.eval(js))
|
51
|
+
values_match? @expected_ruby, @actual_ruby
|
52
|
+
end
|
53
|
+
|
54
|
+
def failure_message
|
55
|
+
"Expected to map to #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
|
56
|
+
end
|
57
|
+
|
58
|
+
def failure_message_when_negated
|
59
|
+
"Expected not to map to #{@actual_ruby.inspect} but did."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module RSpec
|
3
|
+
class ReduceToProxy
|
4
|
+
def initialize(keys, values, rereduce = false)
|
5
|
+
@keys, @values, @rereduce = keys, values, rereduce
|
6
|
+
end
|
7
|
+
|
8
|
+
def to(expected_ruby)
|
9
|
+
ReduceToMatcher.new(expected_ruby, @keys, @values, @rereduce)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ReduceToMatcher
|
14
|
+
include ::RSpec::Matchers::Composable
|
15
|
+
|
16
|
+
def initialize(expected_ruby, keys, values, rereduce = false)
|
17
|
+
@expected_ruby, @keys, @values, @rereduce = expected_ruby, keys, values, rereduce
|
18
|
+
end
|
19
|
+
|
20
|
+
def matches?(view_spec)
|
21
|
+
js = <<-JS
|
22
|
+
(function() {
|
23
|
+
sum = function(values) {
|
24
|
+
var rv = 0;
|
25
|
+
for (var i in values) {
|
26
|
+
rv += values[i];
|
27
|
+
}
|
28
|
+
return rv;
|
29
|
+
};
|
30
|
+
|
31
|
+
var keys = #{@keys.to_json};
|
32
|
+
var values = #{@values.to_json};
|
33
|
+
var reduce = #{view_spec.reduce_function};
|
34
|
+
return JSON.stringify({result: reduce(keys, values, #{@rereduce})});
|
35
|
+
})()
|
36
|
+
JS
|
37
|
+
@actual_ruby = JSON.parse(ExecJS.eval(js))['result']
|
38
|
+
values_match? @expected_ruby, @actual_ruby
|
39
|
+
end
|
40
|
+
|
41
|
+
def failure_message
|
42
|
+
"Expected to reduce to #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
|
43
|
+
end
|
44
|
+
|
45
|
+
def failure_message_when_negated
|
46
|
+
"Expected not to reduce to #{@actual_ruby.inspect} but did."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'execjs'
|
2
|
+
|
3
|
+
require 'couch_potato/rspec/matchers/map_to_matcher'
|
4
|
+
require 'couch_potato/rspec/matchers/reduce_to_matcher'
|
5
|
+
require 'couch_potato/rspec/matchers/map_reduce_to_matcher'
|
6
|
+
require 'couch_potato/rspec/matchers/list_as_matcher'
|
7
|
+
|
8
|
+
module RSpec
|
9
|
+
module Matchers
|
10
|
+
def map(document)
|
11
|
+
CouchPotato::RSpec::MapToProxy.new(document)
|
12
|
+
end
|
13
|
+
|
14
|
+
def reduce(keys, values)
|
15
|
+
CouchPotato::RSpec::ReduceToProxy.new(keys, values)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rereduce(keys, values)
|
19
|
+
CouchPotato::RSpec::ReduceToProxy.new(keys, values, true)
|
20
|
+
end
|
21
|
+
|
22
|
+
def list(results)
|
23
|
+
CouchPotato::RSpec::ListAsProxy.new(results)
|
24
|
+
end
|
25
|
+
|
26
|
+
def map_reduce(*docs)
|
27
|
+
CouchPotato::RSpec::MapReduceToProxy.new(docs)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/mocks'
|
4
|
+
require 'active_support/core_ext/array'
|
5
|
+
|
6
|
+
module CouchPotato::RSpec
|
7
|
+
module StubView
|
8
|
+
class ViewStub
|
9
|
+
include RSpec::Mocks::ExampleMethods
|
10
|
+
|
11
|
+
def initialize(clazz, view, db)
|
12
|
+
@clazz = clazz
|
13
|
+
@view = view
|
14
|
+
@db = db
|
15
|
+
end
|
16
|
+
|
17
|
+
def with(*args, &block)
|
18
|
+
@args = args
|
19
|
+
and_return(block.call) if block
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def and_return(return_value)
|
24
|
+
view_stub = double("#{@clazz}.#{@view}(#{@args.try(:join, ', ')}) view")
|
25
|
+
stub = allow(@clazz).to receive(@view)
|
26
|
+
stub.with(*@args) if @args
|
27
|
+
stub.and_return(view_stub)
|
28
|
+
allow(@db).to receive(:view).with(view_stub).and_return(return_value)
|
29
|
+
return unless return_value.respond_to?(:first)
|
30
|
+
|
31
|
+
allow(@db).to receive(:first).with(view_stub).and_return(return_value.first)
|
32
|
+
allow(@db)
|
33
|
+
.to receive(:view_in_batches) do |_view, batch_size: CouchPotato::Database.default_batch_size, &block|
|
34
|
+
batches = return_value.in_groups_of(batch_size, false)
|
35
|
+
batches.each(&block)
|
36
|
+
end
|
37
|
+
.with(view_stub, any_args)
|
38
|
+
|
39
|
+
if return_value.first
|
40
|
+
allow(@db).to receive(:first!).with(view_stub).and_return(return_value.first)
|
41
|
+
else
|
42
|
+
allow(@db).to receive(:first!).with(view_stub).and_raise(CouchPotato::NotFound)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def stub_view(clazz, view, &block)
|
48
|
+
stub = ViewStub.new clazz, view, self
|
49
|
+
stub.and_return(block.call) if block
|
50
|
+
stub
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module StubDb
|
55
|
+
include ::RSpec::Mocks::ExampleMethods
|
56
|
+
|
57
|
+
def stub_db(options = {})
|
58
|
+
db = double(:db, options)
|
59
|
+
db.extend CouchPotato::RSpec::StubView
|
60
|
+
allow(self).to receive(:database) { db }
|
61
|
+
db
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
::CouchPotato.extend StubDb
|
66
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: couch_potato-rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.
|
4
|
+
version: 4.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Lang
|
@@ -58,6 +58,13 @@ executables: []
|
|
58
58
|
extensions: []
|
59
59
|
extra_rdoc_files: []
|
60
60
|
files:
|
61
|
+
- lib/couch_potato/rspec.rb
|
62
|
+
- lib/couch_potato/rspec/matchers.rb
|
63
|
+
- lib/couch_potato/rspec/matchers/list_as_matcher.rb
|
64
|
+
- lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb
|
65
|
+
- lib/couch_potato/rspec/matchers/map_to_matcher.rb
|
66
|
+
- lib/couch_potato/rspec/matchers/reduce_to_matcher.rb
|
67
|
+
- lib/couch_potato/rspec/stub_db.rb
|
61
68
|
- spec/unit/rspec_matchers_spec.rb
|
62
69
|
homepage: http://github.com/langalex/couch_potato
|
63
70
|
licenses: []
|