Commit 39e44cdf4226b11684fd63254ddb23629d327b78
Merge pull request #22 from ssbc/results-own-domain
Move into results folder.Piet Geursen authored on 4/11/2018, 2:40:26 AM
GitHub committed on 4/11/2018, 2:40:26 AM
Parent: 80c90c52e4088319b7d48db43edeb53d3bba8ffe
Parent: ebad2e906b7c5bfaaf2ef70e09eb2ed8b39f698d
Files changed
poll/async/get.js | changed |
position/sync/buildResults.js | deleted |
test/position/sync/buildResults.test.js | deleted |
test/results/sync/buildResults.test.js | added |
results/sync/buildResults.js | added |
poll/async/get.js | ||
---|---|---|
@@ -3,9 +3,9 @@ | ||
3 | 3 | const { isPoll, isPosition, isChooseOnePoll, isChooseOnePosition } = require('ssb-poll-schema') |
4 | 4 | isPoll.chooseOne = isChooseOnePoll |
5 | 5 | isPosition.chooseOne = isChooseOnePosition |
6 | 6 | const { ERROR_POSITION_TYPE } = require('../../types') |
7 | -const getResults = require('../../position/sync/buildResults') | |
7 | +const getResults = require('../../results/sync/buildResults') | |
8 | 8 | const getMsgContent = require('../../lib/getMsgContent') |
9 | 9 | |
10 | 10 | module.exports = function (server) { |
11 | 11 | return function get (key, cb) { |
position/sync/buildResults.js | ||
---|---|---|
@@ -1,72 +1,0 @@ | ||
1 | -const getMsgContent = require('../../lib/getMsgContent') | |
2 | -const PositionChoiceError = require('../../errors/sync/positionChoiceError') | |
3 | -const PositionLateError = require('../../errors/sync/positionLateError') | |
4 | -const { isChooseOnePoll } = require('ssb-poll-schema') | |
5 | - | |
6 | -// Expects `poll` and `position` objects passed in to be of shape: | |
7 | -// { | |
8 | -// key, | |
9 | -// value: { | |
10 | -// content: {...}, | |
11 | -// timestamp: ... | |
12 | -// author: ... | |
13 | -// } | |
14 | -// } | |
15 | -// | |
16 | -// postions must be of the correct type ie: type checked by the caller. | |
17 | -module.exports = function ({positions, poll}) { | |
18 | - if (isChooseOnePoll(poll)) { | |
19 | - return chooseOneResults({positions, poll}) | |
20 | - } | |
21 | -} | |
22 | - | |
23 | -function chooseOneResults ({positions, poll}) { | |
24 | - var results = getMsgContent(poll) | |
25 | - .details | |
26 | - .choices | |
27 | - .map(choice => { | |
28 | - return { | |
29 | - choice, | |
30 | - voters: {} | |
31 | - } | |
32 | - }) | |
33 | - | |
34 | - return positions.reduce(function (acc, position) { | |
35 | - const { author, content } = position.value | |
36 | - const { choice } = content.details | |
37 | - | |
38 | - if (isInvalidChoice({position, poll})) { | |
39 | - acc.errors.push(PositionChoiceError({position})) | |
40 | - return acc | |
41 | - } | |
42 | - | |
43 | - if (isPositionLate({position, poll})) { | |
44 | - acc.errors.push(PositionLateError({position})) | |
45 | - return acc | |
46 | - } | |
47 | - | |
48 | - deleteExistingVotesByAuthor({results: acc.results, author}) | |
49 | - acc.results[choice].voters[author] = position | |
50 | - | |
51 | - return acc | |
52 | - }, {errors: [], results}) | |
53 | -} | |
54 | - | |
55 | -// !!! assumes these are already sorted by time. | |
56 | -// modifies results passed in | |
57 | -function deleteExistingVotesByAuthor ({author, results}) { | |
58 | - results.forEach(result => { | |
59 | - if (result.voters[author]) { | |
60 | - delete result.voters[author] | |
61 | - } | |
62 | - }) | |
63 | -} | |
64 | - | |
65 | -function isInvalidChoice ({position, poll}) { | |
66 | - const { choice } = position.value.content.details | |
67 | - return choice >= poll.value.content.details.choices.length | |
68 | -} | |
69 | - | |
70 | -function isPositionLate ({position, poll}) { | |
71 | - return position.value.timestamp > poll.value.content.closesAt | |
72 | -} |
test/position/sync/buildResults.test.js | ||
---|---|---|
@@ -1,210 +1,0 @@ | ||
1 | -const test = require('tape') | |
2 | -const pull = require('pull-stream') | |
3 | -const ChooseOne = require('../../../position/async/buildChooseOne')() | |
4 | -const ChooseOnePoll = require('../../../poll/sync/buildChooseOne') | |
5 | -const chooseOneResults = require('../../../position/sync/buildResults') | |
6 | -const {isPosition, isPoll} = require('ssb-poll-schema') | |
7 | -const {ERROR_POSITION_CHOICE, ERROR_POSITION_LATE} = require('../../../types') | |
8 | - | |
9 | -const pietId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/0=.ed25519' | |
10 | -const mixId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/1=.ed25519' | |
11 | -const mikeyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/2=.ed25519' | |
12 | -const timmyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/3=.ed25519' | |
13 | -const tommyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/4=.ed25519' | |
14 | -const sallyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/5=.ed25519' | |
15 | - | |
16 | -const poll = '%t+PhrNxxXkw/jMo6mnwUWfFjJapoPWxzsQoe0Np+nYw=.sha256' | |
17 | - | |
18 | -const now = new Date().toISOString() | |
19 | - | |
20 | -const validPoll = { | |
21 | - key: '%t+PhrNxxXkw/jMo6mnwUWfFjJapoPWxzsQoe0Np+nYw=.sha256', | |
22 | - value: { | |
23 | - content: ChooseOnePoll({ | |
24 | - choices: [1, 2, 'three'], | |
25 | - title: 'how many food', | |
26 | - closesAt: now | |
27 | - }) | |
28 | - } | |
29 | -} | |
30 | - | |
31 | -test('ChooseOneResults - ChooseOneResults', function (t) { | |
32 | - const positions = [ | |
33 | - { value: { content: {choice: 0, poll}, author: pietId } }, | |
34 | - { value: { content: {choice: 0, poll}, author: mixId } }, | |
35 | - { value: { content: {choice: 0, poll}, author: mikeyId } }, | |
36 | - { value: { content: {choice: 1, poll}, author: timmyId } }, | |
37 | - { value: { content: {choice: 1, poll}, author: tommyId } }, | |
38 | - { value: { content: {choice: 2, poll}, author: sallyId } } | |
39 | - ] | |
40 | - | |
41 | - const expected = { | |
42 | - results: [ | |
43 | - { | |
44 | - choice: 1, | |
45 | - voters: { | |
46 | - [pietId]: positions[0], | |
47 | - [mixId]: positions[1], | |
48 | - [mikeyId]: positions[2] | |
49 | - } | |
50 | - }, | |
51 | - { | |
52 | - choice: 2, | |
53 | - voters: { | |
54 | - [timmyId]: positions[3], | |
55 | - [tommyId]: positions[4] | |
56 | - } | |
57 | - }, | |
58 | - { | |
59 | - choice: 'three', | |
60 | - voters: { | |
61 | - [sallyId]: positions[5] | |
62 | - } | |
63 | - } | |
64 | - ], | |
65 | - errors: {} | |
66 | - } | |
67 | - | |
68 | - pull( | |
69 | - pull.values(positions), | |
70 | - pull.asyncMap((fullPosition, cb) => { | |
71 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
72 | - fullPosition.value.content = position | |
73 | - cb(err, fullPosition) | |
74 | - }) | |
75 | - }), | |
76 | - pull.collect(postions => { | |
77 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
78 | - t.deepEqual(actual, expected, 'results are correct') | |
79 | - t.end() | |
80 | - }) | |
81 | - ) | |
82 | -}) | |
83 | - | |
84 | -test('ChooseOneResults - a position stated for an invalid choice index is not counted', function (t) { | |
85 | - const positions = [ | |
86 | - { value: { content: {choice: 3, poll}, author: pietId } } | |
87 | - ] | |
88 | - | |
89 | - pull( | |
90 | - pull.values(positions), | |
91 | - pull.asyncMap((fullPosition, cb) => { | |
92 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
93 | - fullPosition.value.content = position | |
94 | - cb(err, fullPosition) | |
95 | - }) | |
96 | - }), | |
97 | - pull.collect(postions => { | |
98 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
99 | - t.false(actual.results[3], 'invalid vote is not counted') | |
100 | - t.end() | |
101 | - }) | |
102 | - ) | |
103 | -}) | |
104 | - | |
105 | -test('ChooseOneResults - a position stated for an invalid choice index is included in the errors object', function (t) { | |
106 | - const positions = [ | |
107 | - { value: { content: {choice: 3, poll}, author: pietId } } | |
108 | - ] | |
109 | - | |
110 | - pull( | |
111 | - pull.values(positions), | |
112 | - pull.asyncMap((fullPosition, cb) => { | |
113 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
114 | - fullPosition.value.content = position | |
115 | - cb(err, fullPosition) | |
116 | - }) | |
117 | - }), | |
118 | - pull.collect(postions => { | |
119 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
120 | - t.deepEqual(actual.errors[0].type, ERROR_POSITION_CHOICE, 'invalid vote is on error object') | |
121 | - t.end() | |
122 | - }) | |
123 | - ) | |
124 | -}) | |
125 | - | |
126 | -test('ChooseOneResults - A position stated before the closing time of the poll is counted', function (t) { | |
127 | - const positions = [ | |
128 | - { value: { content: {choice: 0, poll}, author: pietId, timestamp: now - 1 } } | |
129 | - ] | |
130 | - | |
131 | - pull( | |
132 | - pull.values(positions), | |
133 | - pull.asyncMap((fullPosition, cb) => { | |
134 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
135 | - fullPosition.value.content = position | |
136 | - cb(err, fullPosition) | |
137 | - }) | |
138 | - }), | |
139 | - pull.collect(postions => { | |
140 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
141 | - t.ok(actual.results[0].voters[pietId], 'valid vote is counted') | |
142 | - t.end() | |
143 | - }) | |
144 | - ) | |
145 | -}) | |
146 | - | |
147 | -test('ChooseOneResults - A position stated after the closing time of the poll is not counted', function (t) { | |
148 | - const positions = [ | |
149 | - { value: { content: {choice: 0, poll}, author: pietId, timestamp: now + 1 } } | |
150 | - ] | |
151 | - | |
152 | - pull( | |
153 | - pull.values(positions), | |
154 | - pull.asyncMap((fullPosition, cb) => { | |
155 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
156 | - fullPosition.value.content = position | |
157 | - cb(err, fullPosition) | |
158 | - }) | |
159 | - }), | |
160 | - pull.collect(postions => { | |
161 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
162 | - t.deepEqual(actual.results[0].voters, {}, 'invalid vote is not counted') | |
163 | - t.end() | |
164 | - }) | |
165 | - ) | |
166 | -}) | |
167 | - | |
168 | -test('ChooseOneResults - A position stated after the closing time of the poll is included in the error object', function (t) { | |
169 | - const positions = [ | |
170 | - { value: { content: {choice: 0, poll}, author: pietId, timestamp: now + 1 } } | |
171 | - ] | |
172 | - | |
173 | - pull( | |
174 | - pull.values(positions), | |
175 | - pull.asyncMap((fullPosition, cb) => { | |
176 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
177 | - fullPosition.value.content = position | |
178 | - cb(err, fullPosition) | |
179 | - }) | |
180 | - }), | |
181 | - pull.collect(postions => { | |
182 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
183 | - t.deepEqual(actual.errors[0].type, ERROR_POSITION_LATE, 'invalid vote is on error object') | |
184 | - t.end() | |
185 | - }) | |
186 | - ) | |
187 | -}) | |
188 | - | |
189 | -test('ChooseOneResults - ChooseOneResults only counts latest vote by an author', function (t) { | |
190 | - const positions = [ | |
191 | - { value: { content: {choice: 2, poll}, author: pietId } }, | |
192 | - { value: { content: {choice: 0, poll}, author: pietId } } | |
193 | - ] | |
194 | - | |
195 | - pull( | |
196 | - pull.values(positions), | |
197 | - pull.asyncMap((fullPosition, cb) => { | |
198 | - ChooseOne(fullPosition.value.content, (err, position) => { | |
199 | - fullPosition.value.content = position | |
200 | - cb(err, fullPosition) | |
201 | - }) | |
202 | - }), | |
203 | - pull.collect(postions => { | |
204 | - const actual = chooseOneResults({positions, poll: validPoll}) | |
205 | - t.false(actual.results[2].voters[pietId], 'old vote is deleted') | |
206 | - t.true(actual.results[0].voters[pietId], 'new vote is counted') | |
207 | - t.end() | |
208 | - }) | |
209 | - ) | |
210 | -}) |
test/results/sync/buildResults.test.js | ||
---|---|---|
@@ -1,0 +1,210 @@ | ||
1 | +const test = require('tape') | |
2 | +const pull = require('pull-stream') | |
3 | +const ChooseOne = require('../../../position/async/buildChooseOne')() | |
4 | +const ChooseOnePoll = require('../../../poll/sync/buildChooseOne') | |
5 | +const chooseOneResults = require('../../../results/sync/buildResults') | |
6 | +const {isPosition, isPoll} = require('ssb-poll-schema') | |
7 | +const {ERROR_POSITION_CHOICE, ERROR_POSITION_LATE} = require('../../../types') | |
8 | + | |
9 | +const pietId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/0=.ed25519' | |
10 | +const mixId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/1=.ed25519' | |
11 | +const mikeyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/2=.ed25519' | |
12 | +const timmyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/3=.ed25519' | |
13 | +const tommyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/4=.ed25519' | |
14 | +const sallyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/5=.ed25519' | |
15 | + | |
16 | +const poll = '%t+PhrNxxXkw/jMo6mnwUWfFjJapoPWxzsQoe0Np+nYw=.sha256' | |
17 | + | |
18 | +const now = new Date().toISOString() | |
19 | + | |
20 | +const validPoll = { | |
21 | + key: '%t+PhrNxxXkw/jMo6mnwUWfFjJapoPWxzsQoe0Np+nYw=.sha256', | |
22 | + value: { | |
23 | + content: ChooseOnePoll({ | |
24 | + choices: [1, 2, 'three'], | |
25 | + title: 'how many food', | |
26 | + closesAt: now | |
27 | + }) | |
28 | + } | |
29 | +} | |
30 | + | |
31 | +test('ChooseOneResults - ChooseOneResults', function (t) { | |
32 | + const positions = [ | |
33 | + { value: { content: {choice: 0, poll}, author: pietId } }, | |
34 | + { value: { content: {choice: 0, poll}, author: mixId } }, | |
35 | + { value: { content: {choice: 0, poll}, author: mikeyId } }, | |
36 | + { value: { content: {choice: 1, poll}, author: timmyId } }, | |
37 | + { value: { content: {choice: 1, poll}, author: tommyId } }, | |
38 | + { value: { content: {choice: 2, poll}, author: sallyId } } | |
39 | + ] | |
40 | + | |
41 | + const expected = { | |
42 | + results: [ | |
43 | + { | |
44 | + choice: 1, | |
45 | + voters: { | |
46 | + [pietId]: positions[0], | |
47 | + [mixId]: positions[1], | |
48 | + [mikeyId]: positions[2] | |
49 | + } | |
50 | + }, | |
51 | + { | |
52 | + choice: 2, | |
53 | + voters: { | |
54 | + [timmyId]: positions[3], | |
55 | + [tommyId]: positions[4] | |
56 | + } | |
57 | + }, | |
58 | + { | |
59 | + choice: 'three', | |
60 | + voters: { | |
61 | + [sallyId]: positions[5] | |
62 | + } | |
63 | + } | |
64 | + ], | |
65 | + errors: {} | |
66 | + } | |
67 | + | |
68 | + pull( | |
69 | + pull.values(positions), | |
70 | + pull.asyncMap((fullPosition, cb) => { | |
71 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
72 | + fullPosition.value.content = position | |
73 | + cb(err, fullPosition) | |
74 | + }) | |
75 | + }), | |
76 | + pull.collect(postions => { | |
77 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
78 | + t.deepEqual(actual, expected, 'results are correct') | |
79 | + t.end() | |
80 | + }) | |
81 | + ) | |
82 | +}) | |
83 | + | |
84 | +test('ChooseOneResults - a position stated for an invalid choice index is not counted', function (t) { | |
85 | + const positions = [ | |
86 | + { value: { content: {choice: 3, poll}, author: pietId } } | |
87 | + ] | |
88 | + | |
89 | + pull( | |
90 | + pull.values(positions), | |
91 | + pull.asyncMap((fullPosition, cb) => { | |
92 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
93 | + fullPosition.value.content = position | |
94 | + cb(err, fullPosition) | |
95 | + }) | |
96 | + }), | |
97 | + pull.collect(postions => { | |
98 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
99 | + t.false(actual.results[3], 'invalid vote is not counted') | |
100 | + t.end() | |
101 | + }) | |
102 | + ) | |
103 | +}) | |
104 | + | |
105 | +test('ChooseOneResults - a position stated for an invalid choice index is included in the errors object', function (t) { | |
106 | + const positions = [ | |
107 | + { value: { content: {choice: 3, poll}, author: pietId } } | |
108 | + ] | |
109 | + | |
110 | + pull( | |
111 | + pull.values(positions), | |
112 | + pull.asyncMap((fullPosition, cb) => { | |
113 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
114 | + fullPosition.value.content = position | |
115 | + cb(err, fullPosition) | |
116 | + }) | |
117 | + }), | |
118 | + pull.collect(postions => { | |
119 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
120 | + t.deepEqual(actual.errors[0].type, ERROR_POSITION_CHOICE, 'invalid vote is on error object') | |
121 | + t.end() | |
122 | + }) | |
123 | + ) | |
124 | +}) | |
125 | + | |
126 | +test('ChooseOneResults - A position stated before the closing time of the poll is counted', function (t) { | |
127 | + const positions = [ | |
128 | + { value: { content: {choice: 0, poll}, author: pietId, timestamp: now - 1 } } | |
129 | + ] | |
130 | + | |
131 | + pull( | |
132 | + pull.values(positions), | |
133 | + pull.asyncMap((fullPosition, cb) => { | |
134 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
135 | + fullPosition.value.content = position | |
136 | + cb(err, fullPosition) | |
137 | + }) | |
138 | + }), | |
139 | + pull.collect(postions => { | |
140 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
141 | + t.ok(actual.results[0].voters[pietId], 'valid vote is counted') | |
142 | + t.end() | |
143 | + }) | |
144 | + ) | |
145 | +}) | |
146 | + | |
147 | +test('ChooseOneResults - A position stated after the closing time of the poll is not counted', function (t) { | |
148 | + const positions = [ | |
149 | + { value: { content: {choice: 0, poll}, author: pietId, timestamp: now + 1 } } | |
150 | + ] | |
151 | + | |
152 | + pull( | |
153 | + pull.values(positions), | |
154 | + pull.asyncMap((fullPosition, cb) => { | |
155 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
156 | + fullPosition.value.content = position | |
157 | + cb(err, fullPosition) | |
158 | + }) | |
159 | + }), | |
160 | + pull.collect(postions => { | |
161 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
162 | + t.deepEqual(actual.results[0].voters, {}, 'invalid vote is not counted') | |
163 | + t.end() | |
164 | + }) | |
165 | + ) | |
166 | +}) | |
167 | + | |
168 | +test('ChooseOneResults - A position stated after the closing time of the poll is included in the error object', function (t) { | |
169 | + const positions = [ | |
170 | + { value: { content: {choice: 0, poll}, author: pietId, timestamp: now + 1 } } | |
171 | + ] | |
172 | + | |
173 | + pull( | |
174 | + pull.values(positions), | |
175 | + pull.asyncMap((fullPosition, cb) => { | |
176 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
177 | + fullPosition.value.content = position | |
178 | + cb(err, fullPosition) | |
179 | + }) | |
180 | + }), | |
181 | + pull.collect(postions => { | |
182 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
183 | + t.deepEqual(actual.errors[0].type, ERROR_POSITION_LATE, 'invalid vote is on error object') | |
184 | + t.end() | |
185 | + }) | |
186 | + ) | |
187 | +}) | |
188 | + | |
189 | +test('ChooseOneResults - ChooseOneResults only counts latest vote by an author', function (t) { | |
190 | + const positions = [ | |
191 | + { value: { content: {choice: 2, poll}, author: pietId } }, | |
192 | + { value: { content: {choice: 0, poll}, author: pietId } } | |
193 | + ] | |
194 | + | |
195 | + pull( | |
196 | + pull.values(positions), | |
197 | + pull.asyncMap((fullPosition, cb) => { | |
198 | + ChooseOne(fullPosition.value.content, (err, position) => { | |
199 | + fullPosition.value.content = position | |
200 | + cb(err, fullPosition) | |
201 | + }) | |
202 | + }), | |
203 | + pull.collect(postions => { | |
204 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
205 | + t.false(actual.results[2].voters[pietId], 'old vote is deleted') | |
206 | + t.true(actual.results[0].voters[pietId], 'new vote is counted') | |
207 | + t.end() | |
208 | + }) | |
209 | + ) | |
210 | +}) |
results/sync/buildResults.js | ||
---|---|---|
@@ -1,0 +1,72 @@ | ||
1 | +const getMsgContent = require('../../lib/getMsgContent') | |
2 | +const PositionChoiceError = require('../../errors/sync/positionChoiceError') | |
3 | +const PositionLateError = require('../../errors/sync/positionLateError') | |
4 | +const { isChooseOnePoll } = require('ssb-poll-schema') | |
5 | + | |
6 | +// Expects `poll` and `position` objects passed in to be of shape: | |
7 | +// { | |
8 | +// key, | |
9 | +// value: { | |
10 | +// content: {...}, | |
11 | +// timestamp: ... | |
12 | +// author: ... | |
13 | +// } | |
14 | +// } | |
15 | +// | |
16 | +// postions must be of the correct type ie: type checked by the caller. | |
17 | +module.exports = function ({positions, poll}) { | |
18 | + if (isChooseOnePoll(poll)) { | |
19 | + return chooseOneResults({positions, poll}) | |
20 | + } | |
21 | +} | |
22 | + | |
23 | +function chooseOneResults ({positions, poll}) { | |
24 | + var results = getMsgContent(poll) | |
25 | + .details | |
26 | + .choices | |
27 | + .map(choice => { | |
28 | + return { | |
29 | + choice, | |
30 | + voters: {} | |
31 | + } | |
32 | + }) | |
33 | + | |
34 | + return positions.reduce(function (acc, position) { | |
35 | + const { author, content } = position.value | |
36 | + const { choice } = content.details | |
37 | + | |
38 | + if (isInvalidChoice({position, poll})) { | |
39 | + acc.errors.push(PositionChoiceError({position})) | |
40 | + return acc | |
41 | + } | |
42 | + | |
43 | + if (isPositionLate({position, poll})) { | |
44 | + acc.errors.push(PositionLateError({position})) | |
45 | + return acc | |
46 | + } | |
47 | + | |
48 | + deleteExistingVotesByAuthor({results: acc.results, author}) | |
49 | + acc.results[choice].voters[author] = position | |
50 | + | |
51 | + return acc | |
52 | + }, {errors: [], results}) | |
53 | +} | |
54 | + | |
55 | +// !!! assumes these are already sorted by time. | |
56 | +// modifies results passed in | |
57 | +function deleteExistingVotesByAuthor ({author, results}) { | |
58 | + results.forEach(result => { | |
59 | + if (result.voters[author]) { | |
60 | + delete result.voters[author] | |
61 | + } | |
62 | + }) | |
63 | +} | |
64 | + | |
65 | +function isInvalidChoice ({position, poll}) { | |
66 | + const { choice } = position.value.content.details | |
67 | + return choice >= poll.value.content.details.choices.length | |
68 | +} | |
69 | + | |
70 | +function isPositionLate ({position, poll}) { | |
71 | + return position.value.timestamp > poll.value.content.closesAt | |
72 | +} |
Built with git-ssb-web