Skip to content

Commit c62218a

Browse files
Sketch a new command
Refs #3184
1 parent a54e006 commit c62218a

3 files changed

Lines changed: 235 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Data, type Option, Schema } from 'effect'
2+
import type * as Commands from '../Commands.ts'
3+
import type * as Preprints from '../Preprints/index.ts'
4+
import type { NonEmptyString, OrcidId, Temporal, Uuid } from '../types/index.ts'
5+
import * as Events from './Events.ts'
6+
7+
export interface Input {
8+
author: {
9+
persona: 'public' | 'pseudonym'
10+
orcidId: OrcidId.OrcidId
11+
}
12+
publishedAt: Temporal.Instant
13+
preprintId: Preprints.IndeterminatePreprintId
14+
rapidPrereviewId: Uuid.Uuid
15+
questions: {
16+
availableCode: 'yes' | 'unsure' | 'not applicable' | 'no'
17+
availableData: 'yes' | 'unsure' | 'not applicable' | 'no'
18+
coherent: 'yes' | 'unsure' | 'not applicable' | 'no'
19+
dataLink: Option.Option<NonEmptyString.NonEmptyString>
20+
ethics: 'yes' | 'unsure' | 'not applicable' | 'no'
21+
future: 'yes' | 'unsure' | 'not applicable' | 'no'
22+
limitations: 'yes' | 'unsure' | 'not applicable' | 'no'
23+
methods: 'yes' | 'unsure' | 'not applicable' | 'no'
24+
newData: 'yes' | 'unsure' | 'not applicable' | 'no'
25+
novel: 'yes' | 'unsure' | 'not applicable' | 'no'
26+
peerReview: 'yes' | 'unsure' | 'not applicable' | 'no'
27+
recommend: 'yes' | 'unsure' | 'not applicable' | 'no'
28+
reproducibility: 'yes' | 'unsure' | 'not applicable' | 'no'
29+
technicalComments: Option.Option<NonEmptyString.NonEmptyString>
30+
editorialComments: Option.Option<NonEmptyString.NonEmptyString>
31+
}
32+
}
33+
34+
export const ImportRapidPrereviewInput: Schema.Schema<Input> = Schema.typeSchema(Events.RapidPrereviewImported).pipe(
35+
Schema.omit('_tag'),
36+
)
37+
38+
type State = RapidPrereviewDoesNotExist | RapidPrereviewAlreadyExists
39+
40+
class RapidPrereviewDoesNotExist extends Data.TaggedClass('RapidPrereviewDoesNotExist') {}
41+
42+
class RapidPrereviewAlreadyExists extends Data.TaggedClass('RapidPrereviewAlreadyExists')<{
43+
author: {
44+
persona: 'public' | 'pseudonym'
45+
orcidId: OrcidId.OrcidId
46+
}
47+
publishedAt: Temporal.Instant
48+
preprintId: Preprints.IndeterminatePreprintId
49+
rapidPrereviewId: Uuid.Uuid
50+
questions: {
51+
availableCode: 'yes' | 'unsure' | 'not applicable' | 'no'
52+
availableData: 'yes' | 'unsure' | 'not applicable' | 'no'
53+
coherent: 'yes' | 'unsure' | 'not applicable' | 'no'
54+
dataLink: Option.Option<NonEmptyString.NonEmptyString>
55+
ethics: 'yes' | 'unsure' | 'not applicable' | 'no'
56+
future: 'yes' | 'unsure' | 'not applicable' | 'no'
57+
limitations: 'yes' | 'unsure' | 'not applicable' | 'no'
58+
methods: 'yes' | 'unsure' | 'not applicable' | 'no'
59+
newData: 'yes' | 'unsure' | 'not applicable' | 'no'
60+
novel: 'yes' | 'unsure' | 'not applicable' | 'no'
61+
peerReview: 'yes' | 'unsure' | 'not applicable' | 'no'
62+
recommend: 'yes' | 'unsure' | 'not applicable' | 'no'
63+
reproducibility: 'yes' | 'unsure' | 'not applicable' | 'no'
64+
technicalComments: Option.Option<NonEmptyString.NonEmptyString>
65+
editorialComments: Option.Option<NonEmptyString.NonEmptyString>
66+
}
67+
}> {}
68+
69+
export class MismatchWithExistingData extends Data.TaggedError('MismatchWithExistingData')<{
70+
author: {
71+
persona: 'public' | 'pseudonym'
72+
orcidId: OrcidId.OrcidId
73+
}
74+
publishedAt: Temporal.Instant
75+
preprintId: Preprints.IndeterminatePreprintId
76+
rapidPrereviewId: Uuid.Uuid
77+
questions: {
78+
availableCode: 'yes' | 'unsure' | 'not applicable' | 'no'
79+
availableData: 'yes' | 'unsure' | 'not applicable' | 'no'
80+
coherent: 'yes' | 'unsure' | 'not applicable' | 'no'
81+
dataLink: Option.Option<NonEmptyString.NonEmptyString>
82+
ethics: 'yes' | 'unsure' | 'not applicable' | 'no'
83+
future: 'yes' | 'unsure' | 'not applicable' | 'no'
84+
limitations: 'yes' | 'unsure' | 'not applicable' | 'no'
85+
methods: 'yes' | 'unsure' | 'not applicable' | 'no'
86+
newData: 'yes' | 'unsure' | 'not applicable' | 'no'
87+
novel: 'yes' | 'unsure' | 'not applicable' | 'no'
88+
peerReview: 'yes' | 'unsure' | 'not applicable' | 'no'
89+
recommend: 'yes' | 'unsure' | 'not applicable' | 'no'
90+
reproducibility: 'yes' | 'unsure' | 'not applicable' | 'no'
91+
technicalComments: Option.Option<NonEmptyString.NonEmptyString>
92+
editorialComments: Option.Option<NonEmptyString.NonEmptyString>
93+
}
94+
}> {}
95+
96+
export declare const ImportRapidPrereview: Commands.Command<
97+
'RapidPrereviewImported',
98+
[Input],
99+
State,
100+
MismatchWithExistingData
101+
>

src/PreprintReviews/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,18 @@
1+
import { Context, Layer } from 'effect'
2+
import * as Commands from '../Commands.ts'
3+
import type { ImportRapidPrereview } from './ImportRapidPrereview.ts'
4+
15
export * from './Errors.ts'
6+
export { ImportRapidPrereviewInput } from './ImportRapidPrereview.ts'
27
export * from './Reactions/index.ts'
8+
9+
export class PreprintReviews extends Context.Tag('PreprintReviews')<
10+
PreprintReviews,
11+
{
12+
importRapidPrereview: Commands.FromCommand<typeof ImportRapidPrereview>
13+
}
14+
>() {}
15+
16+
export const layer = Layer.succeed(PreprintReviews, {
17+
importRapidPrereview: () => new Commands.UnableToHandleCommand({ cause: 'Not implemented' }),
18+
})
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { expect, test } from '@jest/globals'
2+
import { Temporal } from '@js-temporal/polyfill'
3+
import { Either, Option } from 'effect'
4+
import * as Events from '../../src/Events.ts'
5+
import * as _ from '../../src/PreprintReviews/ImportRapidPrereview.ts'
6+
import { BiorxivOrMedrxivPreprintId } from '../../src/Preprints/index.ts'
7+
import { Doi } from '../../src/types/Doi.ts'
8+
import { NonEmptyString } from '../../src/types/NonEmptyString.ts'
9+
import { OrcidId } from '../../src/types/OrcidId.ts'
10+
import { Uuid } from '../../src/types/uuid.ts'
11+
12+
const input = {
13+
author: {
14+
persona: 'public',
15+
orcidId: OrcidId('0000-0002-1825-0097'),
16+
},
17+
publishedAt: Temporal.Now.instant(),
18+
preprintId: new BiorxivOrMedrxivPreprintId({ value: Doi('10.1101/2022.01.13.476201') }),
19+
rapidPrereviewId: Uuid('123e4567-e89b-12d3-a456-426614174000'),
20+
questions: {
21+
availableCode: 'yes',
22+
availableData: 'unsure',
23+
coherent: 'not applicable',
24+
dataLink: Option.none(),
25+
ethics: 'no',
26+
future: 'yes',
27+
limitations: 'unsure',
28+
methods: 'not applicable',
29+
newData: 'no',
30+
novel: 'yes',
31+
peerReview: 'unsure',
32+
recommend: 'not applicable',
33+
reproducibility: 'no',
34+
technicalComments: Option.none(),
35+
editorialComments: Option.none(),
36+
},
37+
} satisfies _.Input
38+
39+
const imported = new Events.RapidPrereviewImported(input)
40+
41+
const importedDifferentId = new Events.RapidPrereviewImported({
42+
...input,
43+
rapidPrereviewId: Uuid('123e4567-e89b-12d3-a456-426614174001'),
44+
})
45+
46+
const importedDifferentPersona = new Events.RapidPrereviewImported({
47+
...input,
48+
author: {
49+
...input.author,
50+
persona: 'pseudonym',
51+
},
52+
})
53+
54+
const importedDifferentPublishedAt = new Events.RapidPrereviewImported({
55+
...input,
56+
publishedAt: Temporal.Now.instant().subtract({ hours: 1 }),
57+
})
58+
59+
const importedDifferentAnswer1 = new Events.RapidPrereviewImported({
60+
...input,
61+
questions: {
62+
...input.questions,
63+
availableCode: 'no',
64+
},
65+
})
66+
67+
const importedDifferentAnswer2 = new Events.RapidPrereviewImported({
68+
...input,
69+
questions: {
70+
...input.questions,
71+
dataLink: Option.some(NonEmptyString('https://example.com/data')),
72+
},
73+
})
74+
75+
test.failing.each<
76+
[string, ReadonlyArray<Events.Event>, _.Input, Either.Either<Option.Option<Events.Event>, _.MismatchWithExistingData>]
77+
>([
78+
['no events', [], input, Either.right(Option.some(new Events.RapidPrereviewImported(input)))],
79+
[
80+
'different Rapid PREreview imported',
81+
[importedDifferentId],
82+
input,
83+
Either.right(Option.some(new Events.RapidPrereviewImported(input))),
84+
],
85+
['already imported, same details', [imported], input, Either.right(Option.none())],
86+
[
87+
'already imported, different persona',
88+
[importedDifferentPersona],
89+
input,
90+
Either.left(new _.MismatchWithExistingData({ ...input, author: importedDifferentPersona.author })),
91+
],
92+
[
93+
'already imported, different published at',
94+
[importedDifferentPublishedAt],
95+
input,
96+
Either.left(new _.MismatchWithExistingData({ ...input, publishedAt: importedDifferentPublishedAt.publishedAt })),
97+
],
98+
[
99+
'already imported, different answer 1',
100+
[importedDifferentAnswer1],
101+
input,
102+
Either.left(new _.MismatchWithExistingData({ ...input, questions: importedDifferentAnswer1.questions })),
103+
],
104+
[
105+
'already imported, different answer 2',
106+
[importedDifferentAnswer2],
107+
input,
108+
Either.left(new _.MismatchWithExistingData({ ...input, questions: importedDifferentAnswer2.questions })),
109+
],
110+
])('%s', (_name, events, input, expected) => {
111+
const { foldState, decide } = _.ImportRapidPrereview
112+
113+
const state = foldState(events, input)
114+
115+
const actual = decide(state, input)
116+
117+
expect(actual).toStrictEqual(expected)
118+
})

0 commit comments

Comments
 (0)