Commit 4ae9c2d21f4b6d8fc91aeb1a890c40180b8a4c7f
Merge branch 'master' of github.com:ssbc/scuttle-poll into poll.async.get
mix irving committed on 3/7/2018, 2:10:31 AMParent: 9db352a29a6d5bac4344c081e9228bd03aaeb97c
Parent: da135887bee9c9d5aa3136c5eec07ede25ed84b6
Files changed
package-lock.json | changed |
package.json | changed |
position/sync/position.js | changed |
position/sync/chooseOneResults.js | added |
test/position/sync/chooseOneResults.test.js | added |
types.js | changed |
package-lock.json | ||
---|---|---|
@@ -281,12 +281,11 @@ | ||
281 | 281 | "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.0.5.tgz", |
282 | 282 | "integrity": "sha1-SOcDGfy0MAkjbpazf5hDiJzntRM=" |
283 | 283 | }, |
284 | 284 | "isarray": { |
285 | - "version": "1.0.0", | |
286 | - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", | |
287 | - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", | |
288 | - "dev": true | |
285 | + "version": "2.0.4", | |
286 | + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz", | |
287 | + "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==" | |
289 | 288 | }, |
290 | 289 | "jsonpointer": { |
291 | 290 | "version": "4.0.1", |
292 | 291 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", |
@@ -428,8 +427,16 @@ | ||
428 | 427 | "process-nextick-args": "2.0.0", |
429 | 428 | "safe-buffer": "5.1.1", |
430 | 429 | "string_decoder": "1.0.3", |
431 | 430 | "util-deprecate": "1.0.2" |
431 | + }, | |
432 | + "dependencies": { | |
433 | + "isarray": { | |
434 | + "version": "1.0.0", | |
435 | + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", | |
436 | + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", | |
437 | + "dev": true | |
438 | + } | |
432 | 439 | } |
433 | 440 | }, |
434 | 441 | "repeat-string": { |
435 | 442 | "version": "1.6.1", |
package.json | ||
---|---|---|
@@ -28,8 +28,9 @@ | ||
28 | 28 | "tape": "^4.8.0" |
29 | 29 | }, |
30 | 30 | "dependencies": { |
31 | 31 | "is-my-json-valid": "^2.17.1", |
32 | + "isarray": "^2.0.4", | |
32 | 33 | "libnested": "^1.2.1", |
33 | 34 | "lodash.clonedeep": "^4.5.0", |
34 | 35 | "ssb-msg-schemas": "^6.3.0", |
35 | 36 | "ssb-ref": "^2.9.0" |
position/sync/position.js | ||
---|---|---|
@@ -1,7 +1,9 @@ | ||
1 | 1 | // var { link } = require('ssb-msg-schemas/util') |
2 | +var getMsgContent = require('../../lib/getMsgContent') | |
2 | 3 | |
3 | -function Position ({ poll, positionDetails, reason, channel, recps, mentions }) { | |
4 | +function Position (msg) { | |
5 | + var { poll, positionDetails, reason, channel, recps, mentions } = getMsgContent(msg) | |
4 | 6 | var content = { type: 'position', poll, positionDetails, reason } |
5 | 7 | |
6 | 8 | // if (root) { |
7 | 9 | // root = link(root) |
position/sync/chooseOneResults.js | ||
---|---|---|
@@ -1,0 +1,46 @@ | ||
1 | +const isArray = require('isarray') | |
2 | +const Position = require('../../position/sync/position') | |
3 | +const {ERROR_POSITION_CHOICE, ERROR_POSITION_TYPE, ERROR_POSITION_LATE} = require('../../types') | |
4 | + | |
5 | +// Expects `poll` and `position` objects passed in to be of shape: | |
6 | +// { | |
7 | +// value: { | |
8 | +// content: {...}, | |
9 | +// timestamp: ... | |
10 | +// author: ... | |
11 | +// } | |
12 | +// } | |
13 | +// | |
14 | +// postions must be of the correct type ie: type checked by the caller. | |
15 | +module.exports = function chooseOneResults ({positions, poll}) { | |
16 | + return positions.reduce(function (results, position) { | |
17 | + const { value: {author} } = position | |
18 | + const { positionDetails: {choice} } = Position(position) | |
19 | + | |
20 | + if (isInvalidChoice({position, poll})) { | |
21 | + results.errors.push({type: ERROR_POSITION_CHOICE, position}) | |
22 | + return results | |
23 | + } | |
24 | + | |
25 | + if (isPositionLate({position, poll})) { | |
26 | + results.errors.push({type: ERROR_POSITION_LATE, position}) | |
27 | + return results | |
28 | + } | |
29 | + | |
30 | + if (!isArray(results[choice])) { | |
31 | + results[choice] = [] | |
32 | + } | |
33 | + results[choice].push(author) | |
34 | + | |
35 | + return results | |
36 | + }, {errors: []}) | |
37 | +} | |
38 | + | |
39 | +function isInvalidChoice ({position, poll}) { | |
40 | + const { positionDetails: {choice} } = Position(position) | |
41 | + return choice >= poll.pollDetails.choices.length | |
42 | +} | |
43 | + | |
44 | +function isPositionLate ({position, poll}) { | |
45 | + return position.value.timestamp > poll.closesAt | |
46 | +} |
test/position/sync/chooseOneResults.test.js | ||
---|---|---|
@@ -1,0 +1,90 @@ | ||
1 | +const test = require('tape') | |
2 | +const ChooseOne = require('../../../position/sync/chooseOne') | |
3 | +const ChooseOnePoll = require('../../../poll/sync/chooseOne') | |
4 | +const Position = require('../../../position/sync/position') | |
5 | +const chooseOneResults = require('../../../position/sync/chooseOneResults') | |
6 | +const {ERROR_POSITION_CHOICE, ERROR_POSITION_TYPE, ERROR_POSITION_LATE} = require('../../../types') | |
7 | + | |
8 | +const pietId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/0=.ed25519' | |
9 | +const mixId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/1=.ed25519' | |
10 | +const mikeyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/2=.ed25519' | |
11 | +const timmyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/3=.ed25519' | |
12 | +const tommyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/4=.ed25519' | |
13 | +const sallyId = '@Mq8D3YC6VdErKQzV3oi2oK5hHSoIwR0hUQr4M46wr/5=.ed25519' | |
14 | + | |
15 | +const poll = '%t+PhrNxxXkw/jMo6mnwUWfFjJapoPWxzsQoe0Np+nYw=.sha256' | |
16 | + | |
17 | +const now = Number(new Date()) | |
18 | + | |
19 | +const validPoll = ChooseOnePoll({ | |
20 | + choices: [1, 2, 'three'], | |
21 | + title: 'how many food', | |
22 | + closesAt: now | |
23 | +}) | |
24 | + | |
25 | +test('ChooseOneResults - ChooseOneResults', function (t) { | |
26 | + const positions = [ | |
27 | + { value: { content: Position(ChooseOne({choice: 0, poll})), author: pietId } }, | |
28 | + { value: { content: Position(ChooseOne({choice: 0, poll})), author: mixId } }, | |
29 | + { value: { content: Position(ChooseOne({choice: 0, poll})), author: mikeyId } }, | |
30 | + { value: { content: Position(ChooseOne({choice: 1, poll})), author: timmyId } }, | |
31 | + { value: { content: Position(ChooseOne({choice: 1, poll})), author: tommyId } }, | |
32 | + { value: { content: Position(ChooseOne({choice: 2, poll})), author: sallyId } } | |
33 | + ] | |
34 | + | |
35 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
36 | + t.deepEqual(actual[0], [pietId, mixId, mikeyId], 'correct voters for choice 0') | |
37 | + t.deepEqual(actual[1], [timmyId, tommyId], 'correct voters for choice 1') | |
38 | + t.deepEqual(actual[2], [sallyId], 'correct voters for choice 2') | |
39 | + t.end() | |
40 | +}) | |
41 | + | |
42 | +test('ChooseOneResults - a position stated for an invalid choice index is not counted', function (t) { | |
43 | + const positions = [ | |
44 | + { value: { content: Position(ChooseOne({choice: 3, poll})), author: pietId } } | |
45 | + ] | |
46 | + | |
47 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
48 | + t.false(actual[3], 'invalid vote is not counted') | |
49 | + t.end() | |
50 | +}) | |
51 | + | |
52 | +test('ChooseOneResults - a position stated for an invalid choice index is included in the errors object', function (t) { | |
53 | + const positions = [ | |
54 | + { value: { content: Position(ChooseOne({choice: 3, poll})), author: pietId } } | |
55 | + ] | |
56 | + | |
57 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
58 | + t.deepEqual(actual.errors[0].type, ERROR_POSITION_CHOICE, 'invalid vote is on error object') | |
59 | + t.end() | |
60 | +}) | |
61 | + | |
62 | +test('ChooseOneResults - A position stated before the closing time of the poll is counted', function (t) { | |
63 | + const positions = [ | |
64 | + { value: { content: Position(ChooseOne({choice: 0, poll})), author: pietId, timestamp: now - 1} } | |
65 | + ] | |
66 | + | |
67 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
68 | + t.true(actual[0], 'valid vote is counted') | |
69 | + t.end() | |
70 | +}) | |
71 | + | |
72 | +test('ChooseOneResults - A position stated after the closing time of the poll is not counted', function (t) { | |
73 | + const positions = [ | |
74 | + { value: { content: Position(ChooseOne({choice: 0, poll})), author: pietId, timestamp: now + 1} } | |
75 | + ] | |
76 | + | |
77 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
78 | + t.false(actual[0], 'invalid vote is not counted') | |
79 | + t.end() | |
80 | +}) | |
81 | + | |
82 | +test('ChooseOneResults - A position stated after the closing time of the poll is included in the error object', function (t) { | |
83 | + const positions = [ | |
84 | + { value: { content: Position(ChooseOne({choice: 0, poll})), author: pietId, timestamp: now + 1} } | |
85 | + ] | |
86 | + | |
87 | + const actual = chooseOneResults({positions, poll: validPoll}) | |
88 | + t.deepEqual(actual.errors[0].type, ERROR_POSITION_LATE, 'invalid vote is on error object') | |
89 | + t.end() | |
90 | +}) |
types.js | ||
---|---|---|
@@ -1,6 +1,9 @@ | ||
1 | 1 | module.exports = { |
2 | - CHOOSE_ONE: 'chooseOne' | |
2 | + CHOOSE_ONE: 'chooseOne', | |
3 | + ERROR_POSITION_TYPE: 'ERROR_POSITION_TYPE', | |
4 | + ERROR_POSITION_LATE: 'ERROR_POSITION_LATE', | |
5 | + ERROR_POSTITION_CHOICE: 'ERROR_POSTITION_CHOICE' | |
3 | 6 | } |
4 | 7 | |
5 | 8 | // Question: do these need to be different, could we just have 'chooseOne', |
6 | 9 | // because we already have: |
Built with git-ssb-web