From d6bc0e7fe32fddfb5e863a37dc0e1c05613dab3f Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Mon, 16 Mar 2020 15:52:17 +0100 Subject: [PATCH 01/11] Add information on available actions to `StudyStatus` (#112) * Include which operations are allowed in `StudyStatus`. * Add helper methods to determine state in `StudyStatus`. * Update TypeScript descriptors for `StudyStatus` and support serializing list. * Make detekt pass. * Refactor: make specific `StudyStatus` inner classes. --- .../carp/studies/application/StudyService.kt | 1 + .../dk/cachet/carp/studies/domain/Study.kt | 10 +++- .../cachet/carp/studies/domain/StudyStatus.kt | 47 +++++++++++++++---- .../studies/application/StudyServiceMock.kt | 5 +- .../studies/application/StudyServiceTest.kt | 11 +++-- .../cachet/carp/studies/domain/StudyTest.kt | 15 +++++- .../studies/infrastructure/StudyStatusTest.kt | 5 +- .../@types/carp.studies.core/index.d.ts | 43 +++++++++++++---- .../index.d.ts | 7 +++ typescript-declarations/tests/VerifyModule.ts | 4 +- .../tests/carp.studies.core.ts | 30 +++++++++++- 11 files changed, 145 insertions(+), 33 deletions(-) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index 69c14c4aa..a7402fd7d 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -60,6 +60,7 @@ interface StudyService * @throws IllegalArgumentException when a study with [studyId] does not exist, * when the provided [protocol] snapshot is invalid, * or when the protocol contains errors preventing it from being used in deployments. + * @throws IllegalStateException when the study protocol can no longer be set since the study went 'live'. */ suspend fun setProtocol( studyId: UUID, protocol: StudyProtocolSnapshot ): StudyStatus diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt index 8ee2faa81..f5b99d39f 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt @@ -73,7 +73,11 @@ class Study( /** * Get the status (serializable) of this [Study]. */ - fun getStatus(): StudyStatus = StudyStatus( id, name, creationDate, canDeployToParticipants, isLive ) + fun getStatus(): StudyStatus = + if ( isLive ) StudyStatus.Live( id, name, creationDate, canDeployToParticipants, canSetStudyProtocol ) + else StudyStatus.Configuring( id, name, creationDate, canDeployToParticipants, canSetStudyProtocol, canGoLive ) + + val canSetStudyProtocol: Boolean get() = !isLive /** * A snapshot of the protocol to use in this study, or null when not yet defined. @@ -89,7 +93,7 @@ class Study( */ set( value ) { - check( !isLive ) + check( !isLive ) { "Can't set protocol since this study already went live." } if ( value != null ) { val protocol = StudyProtocol.fromSnapshot( value ) @@ -107,6 +111,8 @@ class Study( var isLive: Boolean = false private set + private val canGoLive: Boolean get() = protocolSnapshot != null + /** * Lock in the current study protocol so that the study may be deployed to participants. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt index 0a1594480..687237d09 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt @@ -9,22 +9,53 @@ import kotlinx.serialization.Serializable * Describes the status of a [Study]: the number of participants, progress towards study goal, etc. */ @Serializable -data class StudyStatus( - val studyId: UUID, +sealed class StudyStatus +{ + abstract val studyId: UUID /** * A descriptive name for the study, as assigned by the [StudyOwner]. */ - val name: String, + abstract val name: String /** * The date when this study was created. */ - val creationDate: DateTime, + abstract val creationDate: DateTime /** * Determines whether the study in its current state is ready to be deployed to participants. */ - val canDeployToParticipants: Boolean, + abstract val canDeployToParticipants: Boolean /** - * Determines whether a study protocol has been locked in and the study may be deployed to real participants. + * Determines whether a study protocol can be set/changed for the study. */ - val isLive: Boolean -) + abstract val canSetStudyProtocol: Boolean + + + /** + * Study status for when a study is being configured. + */ + @Serializable + data class Configuring( + override val studyId: UUID, + override val name: String, + override val creationDate: DateTime, + override val canDeployToParticipants: Boolean, + override val canSetStudyProtocol: Boolean, + /** + * Determines whether a study is fully configured and can 'go live'. + */ + val canGoLive: Boolean + ) : StudyStatus() + + + /** + * Study status for when a study is 'live'. + */ + @Serializable + data class Live( + override val studyId: UUID, + override val name: String, + override val creationDate: DateTime, + override val canDeployToParticipants: Boolean, + override val canSetStudyProtocol: Boolean + ) : StudyStatus() +} diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index 8d28dc5b0..c9153cce5 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -26,10 +26,11 @@ class StudyServiceMock( { companion object { - private val studyStatus = StudyStatus( + private val studyStatus = StudyStatus.Configuring( UUID.randomUUID(), "Test", DateTime.now(), canDeployToParticipants = false, - isLive = false ) + canSetStudyProtocol = false, + canGoLive = true ) } diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 753039e45..62e64309e 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -9,6 +9,7 @@ import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot import dk.cachet.carp.protocols.domain.devices.Smartphone import dk.cachet.carp.studies.domain.users.StudyOwner import dk.cachet.carp.studies.domain.StudyRepository +import dk.cachet.carp.studies.domain.StudyStatus import dk.cachet.carp.studies.domain.users.AssignParticipantDevices import dk.cachet.carp.test.runBlockingTest import kotlin.test.* @@ -136,7 +137,7 @@ interface StudyServiceTest status = service.setProtocol( status.studyId, createDeployableProtocol() ) assertFalse( status.canDeployToParticipants ) - assertFalse( status.isLive ) + assertTrue( status is StudyStatus.Configuring ) } @Test @@ -174,13 +175,13 @@ interface StudyServiceTest val ( service, _ ) = createService() var status = service.createStudy( StudyOwner(), "Test" ) - assertFalse( status.isLive ) + assertTrue( status is StudyStatus.Configuring ) // Set protocol and go live. service.setProtocol( status.studyId, createDeployableProtocol() ) status = service.goLive( status.studyId ) assertTrue( status.canDeployToParticipants ) - assertTrue( status.isLive ) + assertTrue( status is StudyStatus.Live ) } @Test @@ -239,7 +240,7 @@ interface StudyServiceTest @Test fun deployParticipantGroup_fails_for_unknown_device_roles() = runBlockingTest { val ( service, _ ) = createService() - val ( studyId, protocolSnapshot ) = createLiveStudy( service ) + val ( studyId, _ ) = createLiveStudy( service ) val participant = service.addParticipant( studyId, EmailAddress( "test@test.com" ) ) val assignParticipant = AssignParticipantDevices( participant.id, setOf( "Unknown device" ) ) @@ -249,7 +250,7 @@ interface StudyServiceTest @Test fun deployParticipantGroup_fails_when_not_all_devices_assigned() = runBlockingTest { val ( service, _ ) = createService() - val ( studyId, protocolSnapshot ) = createLiveStudy( service ) + val ( studyId, _ ) = createLiveStudy( service ) val participant = service.addParticipant( studyId, EmailAddress( "test@test.com" ) ) val assignParticipant = AssignParticipantDevices( participant.id, setOf() ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt index 64c471cb8..d7af7b310 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt @@ -38,6 +38,8 @@ class StudyTest { val study = createStudy() + assertTrue( study.canSetStudyProtocol ) + setDeployableProtocol( study ) assertEquals( Study.Event.ProtocolSnapshotChanged( study.protocolSnapshot ), study.consumeEvents().single() ) } @@ -69,7 +71,9 @@ class StudyTest val status = study.getStatus() assertEquals( study.canDeployToParticipants, status.canDeployToParticipants ) - assertEquals( study.isLive, status.isLive ) + assertEquals( study.canSetStudyProtocol, status.canSetStudyProtocol ) + assertTrue( status is StudyStatus.Configuring ) + assertFalse( status.canGoLive ) } @Test @@ -105,6 +109,11 @@ class StudyTest fun goLive_fails_when_no_protocol_set() { val study = createStudy() + val status = study.getStatus() + + assertTrue( status is StudyStatus.Configuring ) + assertFalse( status.canGoLive ) + assertFailsWith { study.goLive() } assertEquals( 0, study.consumeEvents().count() ) } @@ -116,6 +125,8 @@ class StudyTest setDeployableProtocol( study ) study.goLive() + assertTrue( study.canDeployToParticipants ) + val participation = DeanonymizedParticipation( UUID.randomUUID(), Participation( UUID.randomUUID() ) ) study.addParticipation( participation ) assertEquals( Study.Event.ParticipationAdded( participation ), study.consumeEvents().last() ) @@ -127,6 +138,8 @@ class StudyTest val study = createStudy() setDeployableProtocol( study ) + assertFalse( study.canDeployToParticipants ) + val participation = DeanonymizedParticipation( UUID.randomUUID(), Participation( UUID.randomUUID() ) ) assertFailsWith { study.addParticipation( participation ) } val participationEvents = study.consumeEvents().filterIsInstance() diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt index d7adc7baa..9891933fe 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt @@ -14,10 +14,11 @@ class StudyStatusTest @Test fun can_serialize_and_deserialize_study_status_using_JSON() { - val status = StudyStatus( + val status = StudyStatus.Configuring( UUID.randomUUID(), "Test", DateTime.now(), canDeployToParticipants = false, - isLive = false ) + canSetStudyProtocol = true, + canGoLive = false ) val serialized = status.toJson() val parsed = StudyStatus.fromJson( serialized ) diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index 0b13696b8..df9e1ab2b 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -16,22 +16,45 @@ declare module 'carp.studies.core' import StudyInvitation = ddk.cachet.carp.deployment.domain.users.StudyInvitation - namespace dk.cachet.carp.studies.domain { - class StudyStatus + abstract class StudyStatus { - constructor( studyId: UUID, name: string, creationDate: DateTime, canDeployToParticipants: boolean, isLive: boolean ) - static get Companion(): StudyStatus$Companion - - readonly studyId: UUID - readonly name: string - readonly creationDate: DateTime - readonly canDeployToParticipants: boolean - readonly isLive: boolean } interface StudyStatus$Companion { serializer(): any } + + namespace StudyStatus + { + class Configuring + { + constructor( + studyId: UUID, name: string, creationDate: DateTime, + canDeployToParticipants: boolean, + canSetStudyProtocol: boolean, + canGoLive: boolean ) + + readonly studyId: UUID + readonly name: string + readonly creationDate: DateTime + readonly canDeployToParticipants: boolean + readonly canSetStudyProtocol: boolean + readonly canGoLive: boolean + } + class Live + { + constructor( + studyId: UUID, name: string, creationDate: DateTime, + canDeployToParticipants: boolean, + canSetStudyProtocol: boolean ) + + readonly studyId: UUID + readonly name: string + readonly creationDate: DateTime + readonly canDeployToParticipants: boolean + readonly canSetStudyProtocol: boolean + } + } } diff --git a/typescript-declarations/@types/kotlinx-serialization-kotlinx-serialization-runtime/index.d.ts b/typescript-declarations/@types/kotlinx-serialization-kotlinx-serialization-runtime/index.d.ts index ba840a48a..d22631ef3 100644 --- a/typescript-declarations/@types/kotlinx-serialization-kotlinx-serialization-runtime/index.d.ts +++ b/typescript-declarations/@types/kotlinx-serialization-kotlinx-serialization-runtime/index.d.ts @@ -1,5 +1,12 @@ declare module 'kotlinx-serialization-kotlinx-serialization-runtime' { + namespace kotlinx.serialization + { + function get_list_gekvwj$( serializer: any ): any + function get_set_gekvwj$( serializer: any ): any + function get_map_kgqhr1$( serializer: any ): any + } + namespace kotlinx.serialization.json { class Json diff --git a/typescript-declarations/tests/VerifyModule.ts b/typescript-declarations/tests/VerifyModule.ts index 0ad61a4dd..80addb092 100644 --- a/typescript-declarations/tests/VerifyModule.ts +++ b/typescript-declarations/tests/VerifyModule.ts @@ -133,8 +133,8 @@ export default class VerifyModule case AST_NODE_TYPES.ClassProperty: { const scopeToCheck = element.static - ? scope - : this.getInstance( scope.$metadata$.simpleName ) + ? scope + : this.getInstance( scope.name ) this.verifyIdentifier( element.key as Identifier, scopeToCheck ) break; } diff --git a/typescript-declarations/tests/carp.studies.core.ts b/typescript-declarations/tests/carp.studies.core.ts index ddbf86d24..950e69096 100644 --- a/typescript-declarations/tests/carp.studies.core.ts +++ b/typescript-declarations/tests/carp.studies.core.ts @@ -7,6 +7,7 @@ import HashSet = kotlin.collections.HashSet import toSet = kotlin.collections.toSet_us0mfu$ import { kotlinx } from 'kotlinx-serialization-kotlinx-serialization-runtime' import Json = kotlinx.serialization.json.Json +import getListSerializer = kotlinx.serialization.get_list_gekvwj$ import { dk as cdk } from 'carp.common' import DateTime = cdk.cachet.carp.common.DateTime import UUID = cdk.cachet.carp.common.UUID @@ -33,7 +34,8 @@ describe( "carp.studies.core", () => { Participant.Companion, new StudyOwner(), StudyOwner.Companion, - new StudyStatus( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, false ), + new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ), + new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), true, false ), StudyStatus.Companion, StudyServiceRequest.Companion ] @@ -71,6 +73,21 @@ describe( "carp.studies.core", () => { } ) + describe( "StudyStatus", () => { + it ( "can typecheck StudyStatus", () => { + const configuring = new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ) + const configuringStatus: StudyStatus = configuring + expect( configuringStatus instanceof StudyStatus.Configuring ).is.true + expect( configuringStatus instanceof StudyStatus.Live ).is.false + + const live = new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true ) + const liveStatus: StudyStatus = live + expect( liveStatus instanceof StudyStatus.Live ).is.true + expect( liveStatus instanceof StudyStatus.Configuring ).is.false + } ) + } ) + + describe( "StudyServiceRequest", () => { it( "can serialize requests with polymorphic serializer", () => { const createStudy = new StudyServiceRequest.CreateStudy( @@ -98,5 +115,16 @@ describe( "carp.studies.core", () => { const serialized = json.stringify_tf03ej$( serializer, deployGroup ) expect( serialized ).is.not.undefined } ) + + it( "can serialize getStudiesOverview response", () => { + const status = new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ) + const statusList = new ArrayList( [ status ] ) + + const json: Json = createStudiesSerializer() + const serializer = getListSerializer( StudyStatus.Companion.serializer() ) + expect( serializer ).is.not.undefined + const serialized = json.stringify_tf03ej$( serializer, statusList ) + expect( serialized ).is.not.not.undefined + } ) } ) } ) From f0b48ea4b28d15308331dca9c66aa9d95d22130a Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Thu, 19 Mar 2020 21:15:26 +0100 Subject: [PATCH 02/11] Add `StudyService.getProtocol` endpoint. --- .../carp/studies/application/StudyService.kt | 7 +++++++ .../studies/application/StudyServiceHost.kt | 13 +++++++++++++ .../infrastructure/StudyServiceRequest.kt | 5 +++++ .../studies/application/StudyServiceMock.kt | 8 ++++++++ .../studies/application/StudyServiceTest.kt | 19 +++++++++++++++++++ .../StudyServiceRequestsTest.kt | 1 + .../@types/carp.studies.core/index.d.ts | 4 ++++ 7 files changed, 57 insertions(+) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index a7402fd7d..16451febd 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -64,6 +64,13 @@ interface StudyService */ suspend fun setProtocol( studyId: UUID, protocol: StudyProtocolSnapshot ): StudyStatus + /** + * Get the currently specified [StudyProtocolSnapshot] for the study with the specified [studyId]. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + suspend fun getProtocol( studyId: UUID ): StudyProtocolSnapshot? + /** * Lock in the current study protocol so that the study may be deployed to participants. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 45c2ad8b0..74723ab5f 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -117,6 +117,19 @@ class StudyServiceHost( return study.getStatus() } + /** + * Get the currently specified [StudyProtocolSnapshot] for the study with the specified [studyId]. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + override suspend fun getProtocol( studyId: UUID ): StudyProtocolSnapshot? + { + val study: Study? = repository.getById( studyId ) + require( study != null ) + + return study.protocolSnapshot + } + /** * Lock in the current study protocol so that the study may be deployed to participants. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt index 7360aa77a..444398246 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt @@ -50,6 +50,11 @@ sealed class StudyServiceRequest StudyServiceRequest(), ServiceInvoker by createServiceInvoker( StudyService::setProtocol, studyId, protocol ) + @Serializable + data class GetProtocol( val studyId: UUID ) : + StudyServiceRequest(), + ServiceInvoker by createServiceInvoker( StudyService::getProtocol, studyId ) + @Serializable data class GoLive( val studyId: UUID ) : StudyServiceRequest(), diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index c9153cce5..840b8cedb 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -8,6 +8,7 @@ import dk.cachet.carp.deployment.domain.users.StudyInvitation import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot import dk.cachet.carp.studies.domain.users.StudyOwner import dk.cachet.carp.studies.domain.StudyStatus +import dk.cachet.carp.studies.domain.createComplexStudy import dk.cachet.carp.studies.domain.users.AssignParticipantDevices import dk.cachet.carp.studies.domain.users.Participant import dk.cachet.carp.test.Mock @@ -20,6 +21,7 @@ class StudyServiceMock( private val addParticipantResult: Participant = Participant( AccountIdentity.fromEmailAddress( "test@test.com" ) ), private val getParticipantsResult: List = listOf(), private val setProtocolResult: StudyStatus = studyStatus, + private val getProtocolResult: StudyProtocolSnapshot? = createComplexStudy().protocolSnapshot, private val goLiveResult: StudyStatus = studyStatus, private val deployParticipantResult: StudyStatus = studyStatus ) : Mock(), StudyService @@ -70,6 +72,12 @@ class StudyServiceMock( return setProtocolResult } + override suspend fun getProtocol( studyId: UUID ): StudyProtocolSnapshot? + { + trackSuspendCall( StudyService::getProtocol, studyId ) + return getProtocolResult + } + override suspend fun goLive( studyId: UUID ): StudyStatus { trackSuspendCall( StudyService::goLive, studyId ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 62e64309e..7b181ee5a 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -170,6 +170,25 @@ interface StudyServiceTest assertFailsWith { service.setProtocol( status.studyId, protocol.getSnapshot() ) } } + @Test + fun getProtocol_succeeds() = runBlockingTest { + val ( service, _ ) = createService() + val status = service.createStudy( StudyOwner(), "Test" ) + + assertEquals( null, service.getProtocol( status.studyId ) ) + val protocol = createDeployableProtocol() + service.setProtocol( status.studyId, protocol ) + assertEquals( protocol, service.getProtocol( status.studyId ) ) + } + + @Test + fun getProtocol_fails_for_unknown_studyId() = runBlockingTest { + val ( service, _ ) = createService() + + val unknownId = UUID.randomUUID() + assertFailsWith { service.getProtocol( unknownId ) } + } + @Test fun goLive_succeeds() = runBlockingTest { val ( service, _ ) = createService() diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt index 61ac4f38d..161997497 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt @@ -27,6 +27,7 @@ class StudyServiceRequestsTest StudyServiceRequest.AddParticipant( UUID.randomUUID(), EmailAddress( "test@test.com" ) ), StudyServiceRequest.GetParticipants( UUID.randomUUID() ), StudyServiceRequest.SetProtocol( UUID.randomUUID(), StudyProtocol( ProtocolOwner(), "Test" ).getSnapshot() ), + StudyServiceRequest.GetProtocol( UUID.randomUUID() ), StudyServiceRequest.GoLive( UUID.randomUUID() ), StudyServiceRequest.DeployParticipantGroup( UUID.randomUUID(), setOf() ) ) diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index df9e1ab2b..93f5e6cf6 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -136,6 +136,10 @@ declare module 'carp.studies.core' { constructor( studyId: UUID, protocol: StudyProtocolSnapshot ) } + class GetProtocol extends StudyServiceRequest + { + constructor( studyId: UUID ) + } class GoLive extends StudyServiceRequest { constructor( studyId: UUID ) From 43686821be4c6171e37cfe4e16a55ffab9a39a90 Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Thu, 19 Mar 2020 22:50:37 +0100 Subject: [PATCH 03/11] Add `StudyProtocol.updateInternalDescription` endpoint. --- .../carp/studies/application/StudyService.kt | 10 ++++++++++ .../studies/application/StudyServiceHost.kt | 19 +++++++++++++++++++ .../dk/cachet/carp/studies/domain/Study.kt | 3 +++ .../infrastructure/StudyServiceRequest.kt | 5 +++++ .../studies/application/StudyServiceMock.kt | 7 +++++++ .../studies/application/StudyServiceTest.kt | 19 +++++++++++++++++++ .../StudyServiceRequestsTest.kt | 1 + .../@types/carp.studies.core/index.d.ts | 4 ++++ 8 files changed, 68 insertions(+) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index 16451febd..59bf67fdd 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -25,6 +25,16 @@ interface StudyService */ suspend fun createStudy( owner: StudyOwner, name: String, invitation: StudyInvitation? = null ): StudyStatus + /** + * Update study details which are visible only to the [StudyOwner]. + * + * @param studyId The id of the study to update the study details for. + * @param name A descriptive name for the study. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + suspend fun updateInternalDescription( studyId: UUID, name: String ): StudyStatus + /** * Get the status for a study with the given [studyId]. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 74723ab5f..1cd833f54 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -45,6 +45,25 @@ class StudyServiceHost( return study.getStatus() } + /** + * Update study details which are visible only to the [StudyOwner]. + * + * @param studyId The id of the study to update the study details for. + * @param name A descriptive name for the study. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + override suspend fun updateInternalDescription( studyId: UUID, name: String ): StudyStatus + { + val study = repository.getById( studyId ) + require( study != null ) + + study.name = name + repository.update( study ) + + return study.getStatus() + } + /** * Get the status for a study with the given [studyId]. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt index f5b99d39f..b478451f1 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt @@ -57,6 +57,9 @@ class Study( } + /** + * A descriptive name for the study, assigned by, and only visible to, the [StudyOwner]. + */ var name: String = name set( value ) { diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt index 444398246..6c2a7715f 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt @@ -25,6 +25,11 @@ sealed class StudyServiceRequest StudyServiceRequest(), ServiceInvoker by createServiceInvoker( StudyService::createStudy, owner, name, invitation ) + @Serializable + data class UpdateInternalDescription( val studyId: UUID, val name: String ) : + StudyServiceRequest(), + ServiceInvoker by createServiceInvoker( StudyService::updateInternalDescription, studyId, name ) + @Serializable data class GetStudyStatus( val studyId: UUID ) : StudyServiceRequest(), diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index 840b8cedb..8aeb0e8c6 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -16,6 +16,7 @@ import dk.cachet.carp.test.Mock class StudyServiceMock( private val createStudyResult: StudyStatus = studyStatus, + private val updateInternalDescriptionResult: StudyStatus = studyStatus, private val getStudyStatusResult: StudyStatus = studyStatus, private val getStudiesOverviewResult: List = listOf(), private val addParticipantResult: Participant = Participant( AccountIdentity.fromEmailAddress( "test@test.com" ) ), @@ -42,6 +43,12 @@ class StudyServiceMock( return createStudyResult } + override suspend fun updateInternalDescription( studyId: UUID, name: String ): StudyStatus + { + trackSuspendCall( StudyService::updateInternalDescription, studyId, name ) + return updateInternalDescriptionResult + } + override suspend fun getStudyStatus( studyId: UUID ): StudyStatus { trackSuspendCall( StudyService::getStudyStatus, studyId ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 7b181ee5a..c8beebee9 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -59,6 +59,25 @@ interface StudyServiceTest assertFalse( foundStudy.canDeployToParticipants ) } + @Test + fun updateInternalDescription_succeeds() = runBlockingTest { + val ( service, _ ) = createService() + val status = service.createStudy( StudyOwner(), "Test" ) + + val newName = "New name" + val updatedStatus = service.updateInternalDescription( status.studyId, newName ) + assertEquals( newName, updatedStatus.name ) + val retrievedStatus = service.getStudyStatus( status.studyId ) + assertEquals( newName, retrievedStatus.name ) + } + + @Test + fun updateInternalDescription_fails_for_unknown_studyId() = runBlockingTest { + val ( service, _ ) = createService() + + assertFailsWith { service.updateInternalDescription( UUID.randomUUID(), "New name" ) } + } + @Test fun getStudyStatus_succeeds() = runBlockingTest { val ( service, _ ) = createService() diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt index 161997497..6b3cb31b0 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt @@ -22,6 +22,7 @@ class StudyServiceRequestsTest { val requests: List = listOf( StudyServiceRequest.CreateStudy( StudyOwner(), "Test", StudyInvitation.empty() ), + StudyServiceRequest.UpdateInternalDescription( UUID.randomUUID(), "New name" ), StudyServiceRequest.GetStudyStatus( UUID.randomUUID() ), StudyServiceRequest.GetStudiesOverview(StudyOwner()), StudyServiceRequest.AddParticipant( UUID.randomUUID(), EmailAddress( "test@test.com" ) ), diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index 93f5e6cf6..c74a8952f 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -116,6 +116,10 @@ declare module 'carp.studies.core' { constructor( owner: StudyOwner, name: string, invitation: StudyInvitation ) } + class UpdateInternalDescription extends StudyServiceRequest + { + constructor( studyId: UUID, name: string ) + } class GetStudyStatus extends StudyServiceRequest { constructor( studyId: UUID ) From e6e9440a0c0ac3d15dc7c7bef463acd08416556b Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Tue, 24 Mar 2020 17:01:31 +0100 Subject: [PATCH 04/11] Add `description` to `Study`. Currently description cannot yet be retrieved through `StudyService`, for which a failing test is added. --- .../carp/studies/application/StudyService.kt | 6 ++++-- .../studies/application/StudyServiceHost.kt | 9 ++++++--- .../dk/cachet/carp/studies/domain/Study.kt | 20 ++++++++++++++++--- .../carp/studies/domain/StudySnapshot.kt | 2 ++ .../infrastructure/StudyServiceRequest.kt | 8 ++++---- .../studies/application/StudyServiceMock.kt | 8 ++++---- .../studies/application/StudyServiceTest.kt | 10 +++++++--- .../carp/studies/domain/CreateTestObjects.kt | 2 +- .../studies/domain/StudyRepositoryTest.kt | 4 +++- .../cachet/carp/studies/domain/StudyTest.kt | 1 + .../StudyServiceRequestsTest.kt | 4 ++-- .../@types/carp.studies.core/index.d.ts | 4 ++-- .../tests/carp.studies.core.ts | 1 + 13 files changed, 54 insertions(+), 25 deletions(-) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index 59bf67fdd..fd85fd96d 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -19,21 +19,23 @@ interface StudyService * Create a new study for the specified [owner]. * * @param name A descriptive name for the study, assigned by, and only visible to, the [owner]. + * @param description An optional description of the study, assigned by, and only visible to, the [owner]. * @param invitation * An optional description of the study, shared with participants once they are invited. * In case no description is specified, [name] is used as the name in [invitation]. */ - suspend fun createStudy( owner: StudyOwner, name: String, invitation: StudyInvitation? = null ): StudyStatus + suspend fun createStudy( owner: StudyOwner, name: String, description: String = "", invitation: StudyInvitation? = null ): StudyStatus /** * Update study details which are visible only to the [StudyOwner]. * * @param studyId The id of the study to update the study details for. * @param name A descriptive name for the study. + * @param description A description of the study. * * @throws IllegalArgumentException when a study with [studyId] does not exist. */ - suspend fun updateInternalDescription( studyId: UUID, name: String ): StudyStatus + suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus /** * Get the status for a study with the given [studyId]. diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 1cd833f54..0257210e5 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -31,14 +31,15 @@ class StudyServiceHost( * Create a new study for the specified [owner]. * * @param name A descriptive name for the study, assigned by, and only visible to, the [owner]. + * @param description An optional description of the study, assigned by, and only visible to, the [owner]. * @param invitation * An optional description of the study, shared with participants once they are invited. * In case no description is specified, [name] is used as the name in [invitation]. */ - override suspend fun createStudy( owner: StudyOwner, name: String, invitation: StudyInvitation? ): StudyStatus + override suspend fun createStudy( owner: StudyOwner, name: String, description: String, invitation: StudyInvitation? ): StudyStatus { val ensuredInvitation = invitation ?: StudyInvitation( name ) - val study = Study( owner, name, ensuredInvitation ) + val study = Study( owner, name, description, ensuredInvitation ) repository.add( study ) @@ -50,15 +51,17 @@ class StudyServiceHost( * * @param studyId The id of the study to update the study details for. * @param name A descriptive name for the study. + * @param description A description of the study. * * @throws IllegalArgumentException when a study with [studyId] does not exist. */ - override suspend fun updateInternalDescription( studyId: UUID, name: String ): StudyStatus + override suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus { val study = repository.getById( studyId ) require( study != null ) study.name = name + study.description = description repository.update( study ) return study.getStatus() diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt index b478451f1..8ff42f651 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt @@ -26,6 +26,10 @@ class Study( * A descriptive name for the study, assigned by, and only visible to, the [StudyOwner]. */ name: String, + /** + * A description for the study, assigned by, and only visible to, the [StudyOwner]. + */ + description: String = "", /** * A description of the study, shared with participants once they are invited to the study. */ @@ -35,7 +39,7 @@ class Study( { sealed class Event : Immutable() { - data class NameChanged( val name: String ) : Event() + data class InternalDescriptionChanged( val name: String, val description: String ) : Event() data class ProtocolSnapshotChanged( val protocolSnapshot: StudyProtocolSnapshot? ) : Event() data class StateChanged( val isLive: Boolean ) : Event() data class ParticipationAdded( val participation: DeanonymizedParticipation ) : Event() @@ -46,7 +50,7 @@ class Study( { fun fromSnapshot( snapshot: StudySnapshot ): Study { - val study = Study( StudyOwner( snapshot.ownerId ), snapshot.name, snapshot.invitation, snapshot.studyId ) + val study = Study( StudyOwner( snapshot.ownerId ), snapshot.name, snapshot.description, snapshot.invitation, snapshot.studyId ) study.creationDate = snapshot.creationDate study.protocolSnapshot = snapshot.protocolSnapshot study.isLive = snapshot.isLive @@ -64,7 +68,17 @@ class Study( set( value ) { field = value - event( Event.NameChanged( name ) ) + event( Event.InternalDescriptionChanged( name, description ) ) + } + + /** + * A description for the study, assigned by, and only visible to, the [StudyOwner]. + */ + var description: String = description + set( value ) + { + field = value + event( Event.InternalDescriptionChanged( name, description ) ) } /** diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudySnapshot.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudySnapshot.kt index 9cae71a71..a0fbf52c9 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudySnapshot.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudySnapshot.kt @@ -14,6 +14,7 @@ data class StudySnapshot( val studyId: UUID, val ownerId: UUID, val name: String, + val description: String, val invitation: StudyInvitation, val creationDate: DateTime, val protocolSnapshot: StudyProtocolSnapshot?, @@ -34,6 +35,7 @@ data class StudySnapshot( studyId = study.id, ownerId = study.owner.id, name = study.name, + description = study.description, invitation = study.invitation, creationDate = study.creationDate, protocolSnapshot = study.protocolSnapshot, diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt index 6c2a7715f..2c90f1dc1 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt @@ -21,14 +21,14 @@ import kotlinx.serialization.Serializable sealed class StudyServiceRequest { @Serializable - data class CreateStudy( val owner: StudyOwner, val name: String, val invitation: StudyInvitation? = null ) : + data class CreateStudy( val owner: StudyOwner, val name: String, val description: String = "", val invitation: StudyInvitation? = null ) : StudyServiceRequest(), - ServiceInvoker by createServiceInvoker( StudyService::createStudy, owner, name, invitation ) + ServiceInvoker by createServiceInvoker( StudyService::createStudy, owner, name, description, invitation ) @Serializable - data class UpdateInternalDescription( val studyId: UUID, val name: String ) : + data class UpdateInternalDescription( val studyId: UUID, val name: String, val description: String ) : StudyServiceRequest(), - ServiceInvoker by createServiceInvoker( StudyService::updateInternalDescription, studyId, name ) + ServiceInvoker by createServiceInvoker( StudyService::updateInternalDescription, studyId, name, description ) @Serializable data class GetStudyStatus( val studyId: UUID ) : diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index 8aeb0e8c6..c22f770a4 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -37,15 +37,15 @@ class StudyServiceMock( } - override suspend fun createStudy( owner: StudyOwner, name: String, invitation: StudyInvitation? ): StudyStatus + override suspend fun createStudy( owner: StudyOwner, name: String, description: String, invitation: StudyInvitation? ): StudyStatus { - trackSuspendCall( StudyService::createStudy, owner, name, invitation ) + trackSuspendCall( StudyService::createStudy, owner, name, description, invitation ) return createStudyResult } - override suspend fun updateInternalDescription( studyId: UUID, name: String ): StudyStatus + override suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus { - trackSuspendCall( StudyService::updateInternalDescription, studyId, name ) + trackSuspendCall( StudyService::updateInternalDescription, studyId, name, description ) return updateInternalDescriptionResult } diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index c8beebee9..2f2fcc680 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -49,12 +49,14 @@ interface StudyServiceTest val owner = StudyOwner() val name = "Test" + val description = "Description" val invitation = StudyInvitation( "Lorem ipsum" ) - val status = service.createStudy( owner, name, invitation ) + val status = service.createStudy( owner, name, description, invitation ) val foundStudy = repo.getById( status.studyId )!! assertEquals( status.studyId, foundStudy.id ) assertEquals( name, foundStudy.name ) + assertEquals( description, foundStudy.description ) assertEquals( invitation, foundStudy.invitation ) assertFalse( foundStudy.canDeployToParticipants ) } @@ -65,8 +67,10 @@ interface StudyServiceTest val status = service.createStudy( StudyOwner(), "Test" ) val newName = "New name" - val updatedStatus = service.updateInternalDescription( status.studyId, newName ) + val newDescription = "New description" + val updatedStatus = service.updateInternalDescription( status.studyId, newName, newDescription ) assertEquals( newName, updatedStatus.name ) + fail( "Cannot retrieve description." ) val retrievedStatus = service.getStudyStatus( status.studyId ) assertEquals( newName, retrievedStatus.name ) } @@ -75,7 +79,7 @@ interface StudyServiceTest fun updateInternalDescription_fails_for_unknown_studyId() = runBlockingTest { val ( service, _ ) = createService() - assertFailsWith { service.updateInternalDescription( UUID.randomUUID(), "New name" ) } + assertFailsWith { service.updateInternalDescription( UUID.randomUUID(), "New name", "New description" ) } } @Test diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/CreateTestObjects.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/CreateTestObjects.kt index dfa68c6c7..5d10e5e97 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/CreateTestObjects.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/CreateTestObjects.kt @@ -17,7 +17,7 @@ fun createComplexStudy(): Study { val owner = StudyOwner() val invitation = StudyInvitation.empty() - val study = Study( owner, "Test", invitation ) + val study = Study( owner, "Test", "Description", invitation ) // Specify protocol. val protocol = StudyProtocol( ProtocolOwner(), "Test protocol" ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt index 28a339fda..ce4523499 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt @@ -22,7 +22,7 @@ interface StudyRepositoryTest val repo = createRepository() val study = addStudy( repo ) - val studyWithSameId = Study( StudyOwner(), "Study 2", StudyInvitation.empty(), study.id ) + val studyWithSameId = Study( StudyOwner(), "Study 2", "Description", StudyInvitation.empty(), study.id ) assertFailsWith { repo.add( studyWithSameId ) @@ -70,10 +70,12 @@ interface StudyRepositoryTest repo.add( study ) study.name = "Changed name" + study.description = "Changed description" repo.update( study ) val updatedStudy = repo.getById( study.id ) assertNotNull( updatedStudy ) assertEquals( "Changed name", updatedStudy.name ) + assertEquals( "Changed description", updatedStudy.description ) } @Test diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt index d7af7b310..440fb7ea0 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt @@ -26,6 +26,7 @@ class StudyTest assertEquals( study.id, fromSnapshot.id ) assertEquals( study.owner, fromSnapshot.owner ) assertEquals( study.name, fromSnapshot.name ) + assertEquals( study.description, fromSnapshot.description ) assertEquals( study.invitation, fromSnapshot.invitation ) assertEquals( study.creationDate, fromSnapshot.creationDate ) assertEquals( study.protocolSnapshot, fromSnapshot.protocolSnapshot ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt index 6b3cb31b0..d0aa96ac1 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt @@ -21,8 +21,8 @@ class StudyServiceRequestsTest companion object { val requests: List = listOf( - StudyServiceRequest.CreateStudy( StudyOwner(), "Test", StudyInvitation.empty() ), - StudyServiceRequest.UpdateInternalDescription( UUID.randomUUID(), "New name" ), + StudyServiceRequest.CreateStudy( StudyOwner(), "Test", "Description", StudyInvitation.empty() ), + StudyServiceRequest.UpdateInternalDescription( UUID.randomUUID(), "New name", "New description" ), StudyServiceRequest.GetStudyStatus( UUID.randomUUID() ), StudyServiceRequest.GetStudiesOverview(StudyOwner()), StudyServiceRequest.AddParticipant( UUID.randomUUID(), EmailAddress( "test@test.com" ) ), diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index c74a8952f..a33047518 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -114,11 +114,11 @@ declare module 'carp.studies.core' { class CreateStudy extends StudyServiceRequest { - constructor( owner: StudyOwner, name: string, invitation: StudyInvitation ) + constructor( owner: StudyOwner, name: string, description: string, invitation: StudyInvitation ) } class UpdateInternalDescription extends StudyServiceRequest { - constructor( studyId: UUID, name: string ) + constructor( studyId: UUID, name: string, description: string ) } class GetStudyStatus extends StudyServiceRequest { diff --git a/typescript-declarations/tests/carp.studies.core.ts b/typescript-declarations/tests/carp.studies.core.ts index 950e69096..6afbcaf09 100644 --- a/typescript-declarations/tests/carp.studies.core.ts +++ b/typescript-declarations/tests/carp.studies.core.ts @@ -93,6 +93,7 @@ describe( "carp.studies.core", () => { const createStudy = new StudyServiceRequest.CreateStudy( new StudyOwner(), "Test study", + "This is a study description", StudyInvitation.Companion.empty() ) From 762fcc62b39b7e9dc8c0e8ca61597f2574609649 Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Tue, 24 Mar 2020 19:03:52 +0100 Subject: [PATCH 05/11] Replace `StudyService.getProtocol` with more complete `getStudyDetails`. --- .../carp/studies/application/StudyService.kt | 15 +++---- .../studies/application/StudyServiceHost.kt | 35 ++++++++++------ .../carp/studies/domain/StudyDetails.kt | 41 +++++++++++++++++++ .../infrastructure/StudyServiceRequest.kt | 11 ++--- .../studies/application/StudyServiceMock.kt | 18 ++++---- .../studies/application/StudyServiceTest.kt | 39 ++++++++---------- .../StudyServiceRequestsTest.kt | 2 +- .../@types/carp.studies.core/index.d.ts | 30 ++++++++++++-- .../tests/carp.studies.core.ts | 3 ++ 9 files changed, 134 insertions(+), 60 deletions(-) create mode 100644 carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyDetails.kt diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index fd85fd96d..80edf27ca 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -4,6 +4,7 @@ import dk.cachet.carp.common.EmailAddress import dk.cachet.carp.common.UUID import dk.cachet.carp.deployment.domain.users.StudyInvitation import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot +import dk.cachet.carp.studies.domain.StudyDetails import dk.cachet.carp.studies.domain.users.StudyOwner import dk.cachet.carp.studies.domain.StudyStatus import dk.cachet.carp.studies.domain.users.AssignParticipantDevices @@ -37,6 +38,13 @@ interface StudyService */ suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus + /** + * Gets detailed information about the study with the specified [studyId], including which study protocol is set. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + suspend fun getStudyDetails( studyId: UUID ): StudyDetails + /** * Get the status for a study with the given [studyId]. * @@ -76,13 +84,6 @@ interface StudyService */ suspend fun setProtocol( studyId: UUID, protocol: StudyProtocolSnapshot ): StudyStatus - /** - * Get the currently specified [StudyProtocolSnapshot] for the study with the specified [studyId]. - * - * @throws IllegalArgumentException when a study with [studyId] does not exist. - */ - suspend fun getProtocol( studyId: UUID ): StudyProtocolSnapshot? - /** * Lock in the current study protocol so that the study may be deployed to participants. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 0257210e5..51356af66 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -9,6 +9,7 @@ import dk.cachet.carp.deployment.domain.users.StudyInvitation import dk.cachet.carp.protocols.domain.InvalidConfigurationError import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot import dk.cachet.carp.studies.domain.Study +import dk.cachet.carp.studies.domain.StudyDetails import dk.cachet.carp.studies.domain.StudyRepository import dk.cachet.carp.studies.domain.StudyStatus import dk.cachet.carp.studies.domain.users.AssignParticipantDevices @@ -67,6 +68,27 @@ class StudyServiceHost( return study.getStatus() } + /** + * Gets detailed information about the study with the specified [studyId], including which study protocol is set. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + override suspend fun getStudyDetails( studyId: UUID ): StudyDetails + { + val study: Study? = repository.getById( studyId ) + require( study != null ) + + return StudyDetails( + study.id, + study.owner, + study.name, + study.creationDate, + study.description, + study.invitation, + study.protocolSnapshot + ) + } + /** * Get the status for a study with the given [studyId]. * @@ -139,19 +161,6 @@ class StudyServiceHost( return study.getStatus() } - /** - * Get the currently specified [StudyProtocolSnapshot] for the study with the specified [studyId]. - * - * @throws IllegalArgumentException when a study with [studyId] does not exist. - */ - override suspend fun getProtocol( studyId: UUID ): StudyProtocolSnapshot? - { - val study: Study? = repository.getById( studyId ) - require( study != null ) - - return study.protocolSnapshot - } - /** * Lock in the current study protocol so that the study may be deployed to participants. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyDetails.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyDetails.kt new file mode 100644 index 000000000..0ff8567f3 --- /dev/null +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyDetails.kt @@ -0,0 +1,41 @@ +package dk.cachet.carp.studies.domain + +import dk.cachet.carp.common.DateTime +import dk.cachet.carp.common.UUID +import dk.cachet.carp.deployment.domain.users.StudyInvitation +import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot +import dk.cachet.carp.studies.domain.users.StudyOwner +import kotlinx.serialization.Serializable + + +/** + * Contains detailed information about a [Study], such as the configured study protocol. + */ +@Serializable +data class StudyDetails( + val studyId: UUID, + /** + * The person or group that created this [Study]. + */ + val studyOwner: StudyOwner, + /** + * A descriptive name for the study, assigned by, and only visible to, the [StudyOwner]. + */ + val name: String, + /** + * The date when this study was created. + */ + val creationDate: DateTime, + /** + * A description for the study, assigned by, and only visible to, the [StudyOwner]. + */ + val description: String, + /** + * A description of the study, shared with participants once they are invited to the study. + */ + val invitation: StudyInvitation, + /** + * A snapshot of the protocol to use in this study, or null when not yet defined. + */ + val protocolSnapshot: StudyProtocolSnapshot? +) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt index 2c90f1dc1..3223777e7 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt @@ -7,6 +7,7 @@ import dk.cachet.carp.common.ddd.ServiceInvoker import dk.cachet.carp.deployment.domain.users.StudyInvitation import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot import dk.cachet.carp.studies.application.StudyService +import dk.cachet.carp.studies.domain.StudyDetails import dk.cachet.carp.studies.domain.users.StudyOwner import dk.cachet.carp.studies.domain.StudyStatus import dk.cachet.carp.studies.domain.users.AssignParticipantDevices @@ -30,6 +31,11 @@ sealed class StudyServiceRequest StudyServiceRequest(), ServiceInvoker by createServiceInvoker( StudyService::updateInternalDescription, studyId, name, description ) + @Serializable + data class GetStudyDetails( val studyId: UUID ) : + StudyServiceRequest(), + ServiceInvoker by createServiceInvoker( StudyService::getStudyDetails, studyId ) + @Serializable data class GetStudyStatus( val studyId: UUID ) : StudyServiceRequest(), @@ -55,11 +61,6 @@ sealed class StudyServiceRequest StudyServiceRequest(), ServiceInvoker by createServiceInvoker( StudyService::setProtocol, studyId, protocol ) - @Serializable - data class GetProtocol( val studyId: UUID ) : - StudyServiceRequest(), - ServiceInvoker by createServiceInvoker( StudyService::getProtocol, studyId ) - @Serializable data class GoLive( val studyId: UUID ) : StudyServiceRequest(), diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index c22f770a4..09938dcc1 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -6,6 +6,7 @@ import dk.cachet.carp.common.UUID import dk.cachet.carp.common.users.AccountIdentity import dk.cachet.carp.deployment.domain.users.StudyInvitation import dk.cachet.carp.protocols.domain.StudyProtocolSnapshot +import dk.cachet.carp.studies.domain.StudyDetails import dk.cachet.carp.studies.domain.users.StudyOwner import dk.cachet.carp.studies.domain.StudyStatus import dk.cachet.carp.studies.domain.createComplexStudy @@ -17,12 +18,15 @@ import dk.cachet.carp.test.Mock class StudyServiceMock( private val createStudyResult: StudyStatus = studyStatus, private val updateInternalDescriptionResult: StudyStatus = studyStatus, + private val getStudyDetailsResult: StudyDetails = StudyDetails( + UUID.randomUUID(), StudyOwner(), "Name", DateTime.now(), + "Description", StudyInvitation.empty(), createComplexStudy().protocolSnapshot + ), private val getStudyStatusResult: StudyStatus = studyStatus, private val getStudiesOverviewResult: List = listOf(), private val addParticipantResult: Participant = Participant( AccountIdentity.fromEmailAddress( "test@test.com" ) ), private val getParticipantsResult: List = listOf(), private val setProtocolResult: StudyStatus = studyStatus, - private val getProtocolResult: StudyProtocolSnapshot? = createComplexStudy().protocolSnapshot, private val goLiveResult: StudyStatus = studyStatus, private val deployParticipantResult: StudyStatus = studyStatus ) : Mock(), StudyService @@ -49,6 +53,12 @@ class StudyServiceMock( return updateInternalDescriptionResult } + override suspend fun getStudyDetails( studyId: UUID ): StudyDetails + { + trackSuspendCall( StudyService::getStudyDetails, studyId ) + return getStudyDetailsResult + } + override suspend fun getStudyStatus( studyId: UUID ): StudyStatus { trackSuspendCall( StudyService::getStudyStatus, studyId ) @@ -79,12 +89,6 @@ class StudyServiceMock( return setProtocolResult } - override suspend fun getProtocol( studyId: UUID ): StudyProtocolSnapshot? - { - trackSuspendCall( StudyService::getProtocol, studyId ) - return getProtocolResult - } - override suspend fun goLive( studyId: UUID ): StudyStatus { trackSuspendCall( StudyService::goLive, studyId ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 2f2fcc680..2f73fcaf2 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -70,9 +70,9 @@ interface StudyServiceTest val newDescription = "New description" val updatedStatus = service.updateInternalDescription( status.studyId, newName, newDescription ) assertEquals( newName, updatedStatus.name ) - fail( "Cannot retrieve description." ) - val retrievedStatus = service.getStudyStatus( status.studyId ) - assertEquals( newName, retrievedStatus.name ) + val studyDetails = service.getStudyDetails( status.studyId ) + assertEquals( newName, studyDetails.name ) + assertEquals( newDescription, studyDetails.description ) } @Test @@ -82,6 +82,14 @@ interface StudyServiceTest assertFailsWith { service.updateInternalDescription( UUID.randomUUID(), "New name", "New description" ) } } + @Test + fun getStudyDetails_fails_for_unknown_studyId() = runBlockingTest { + val ( service, _ ) = createService() + + val unknownId = UUID.randomUUID() + assertFailsWith { service.getStudyDetails( unknownId ) } + } + @Test fun getStudyStatus_succeeds() = runBlockingTest { val ( service, _ ) = createService() @@ -158,9 +166,13 @@ interface StudyServiceTest val ( service, _ ) = createService() var status = service.createStudy( StudyOwner(), "Test" ) - status = service.setProtocol( status.studyId, createDeployableProtocol() ) + val protocol = createDeployableProtocol() + status = service.setProtocol( status.studyId, protocol ) assertFalse( status.canDeployToParticipants ) assertTrue( status is StudyStatus.Configuring ) + + val details = service.getStudyDetails( status.studyId ) + assertEquals( protocol, details.protocolSnapshot ) } @Test @@ -193,25 +205,6 @@ interface StudyServiceTest assertFailsWith { service.setProtocol( status.studyId, protocol.getSnapshot() ) } } - @Test - fun getProtocol_succeeds() = runBlockingTest { - val ( service, _ ) = createService() - val status = service.createStudy( StudyOwner(), "Test" ) - - assertEquals( null, service.getProtocol( status.studyId ) ) - val protocol = createDeployableProtocol() - service.setProtocol( status.studyId, protocol ) - assertEquals( protocol, service.getProtocol( status.studyId ) ) - } - - @Test - fun getProtocol_fails_for_unknown_studyId() = runBlockingTest { - val ( service, _ ) = createService() - - val unknownId = UUID.randomUUID() - assertFailsWith { service.getProtocol( unknownId ) } - } - @Test fun goLive_succeeds() = runBlockingTest { val ( service, _ ) = createService() diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt index d0aa96ac1..c98c0ef15 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt @@ -23,12 +23,12 @@ class StudyServiceRequestsTest val requests: List = listOf( StudyServiceRequest.CreateStudy( StudyOwner(), "Test", "Description", StudyInvitation.empty() ), StudyServiceRequest.UpdateInternalDescription( UUID.randomUUID(), "New name", "New description" ), + StudyServiceRequest.GetStudyDetails( UUID.randomUUID() ), StudyServiceRequest.GetStudyStatus( UUID.randomUUID() ), StudyServiceRequest.GetStudiesOverview(StudyOwner()), StudyServiceRequest.AddParticipant( UUID.randomUUID(), EmailAddress( "test@test.com" ) ), StudyServiceRequest.GetParticipants( UUID.randomUUID() ), StudyServiceRequest.SetProtocol( UUID.randomUUID(), StudyProtocol( ProtocolOwner(), "Test" ).getSnapshot() ), - StudyServiceRequest.GetProtocol( UUID.randomUUID() ), StudyServiceRequest.GoLive( UUID.randomUUID() ), StudyServiceRequest.DeployParticipantGroup( UUID.randomUUID(), setOf() ) ) diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index a33047518..325991a3a 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -18,6 +18,28 @@ declare module 'carp.studies.core' namespace dk.cachet.carp.studies.domain { + import StudyOwner = dk.cachet.carp.studies.domain.users.StudyOwner + + class StudyDetails + { + constructor( + studyId: UUID, studyOwner: StudyOwner, name: string, creationDate: DateTime, + description: string, + invitation: StudyInvitation, + protocolSnapshot: StudyProtocolSnapshot | null ) + + static get Companion(): StudyDetails$Companion + + readonly studyId: UUID + readonly studyOwner: StudyOwner + readonly name: string + readonly creationDate: DateTime + readonly description: string + readonly invitation: StudyInvitation + readonly protocolSnapshot: StudyProtocolSnapshot | null + } + interface StudyDetails$Companion { serializer(): any } + abstract class StudyStatus { static get Companion(): StudyStatus$Companion @@ -120,6 +142,10 @@ declare module 'carp.studies.core' { constructor( studyId: UUID, name: string, description: string ) } + class GetStudyDetails extends StudyServiceRequest + { + constructor( studyId: UUID ) + } class GetStudyStatus extends StudyServiceRequest { constructor( studyId: UUID ) @@ -140,10 +166,6 @@ declare module 'carp.studies.core' { constructor( studyId: UUID, protocol: StudyProtocolSnapshot ) } - class GetProtocol extends StudyServiceRequest - { - constructor( studyId: UUID ) - } class GoLive extends StudyServiceRequest { constructor( studyId: UUID ) diff --git a/typescript-declarations/tests/carp.studies.core.ts b/typescript-declarations/tests/carp.studies.core.ts index 6afbcaf09..2a3190501 100644 --- a/typescript-declarations/tests/carp.studies.core.ts +++ b/typescript-declarations/tests/carp.studies.core.ts @@ -20,6 +20,7 @@ import getAssignedParticipantIds = dk.cachet.carp.studies.domain.users.participa import getAssignedDeviceRoles = dk.cachet.carp.studies.domain.users.deviceRoles_nvx6bb$ import Participant = dk.cachet.carp.studies.domain.users.Participant import StudyOwner = dk.cachet.carp.studies.domain.users.StudyOwner +import StudyDetails = dk.cachet.carp.studies.domain.StudyDetails import StudyStatus = dk.cachet.carp.studies.domain.StudyStatus import StudyServiceRequest = dk.cachet.carp.studies.infrastructure.StudyServiceRequest import createStudiesSerializer = dk.cachet.carp.studies.infrastructure.createStudiesSerializer_stpyu4$ @@ -34,6 +35,8 @@ describe( "carp.studies.core", () => { Participant.Companion, new StudyOwner(), StudyOwner.Companion, + new StudyDetails( UUID.Companion.randomUUID(), new StudyOwner(), "Name", DateTime.Companion.now(), "Description", StudyInvitation.Companion.empty(), null ), + StudyDetails.Companion, new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ), new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), true, false ), StudyStatus.Companion, From 2ea49f18bd4e71adc84107623de8d7a86a6b9cfc Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Wed, 25 Mar 2020 17:15:55 +0100 Subject: [PATCH 06/11] Add `StudyProtocol.setInvitation` endpoint. --- .../carp/studies/application/StudyService.kt | 8 ++++ .../studies/application/StudyServiceHost.kt | 18 +++++++++ .../dk/cachet/carp/studies/domain/Study.kt | 26 ++++++++++-- .../cachet/carp/studies/domain/StudyStatus.kt | 16 +++++--- .../infrastructure/StudyServiceRequest.kt | 5 +++ .../studies/application/StudyServiceMock.kt | 10 ++++- .../studies/application/StudyServiceTest.kt | 35 ++++++++++++++++ .../studies/domain/StudyRepositoryTest.kt | 3 ++ .../cachet/carp/studies/domain/StudyTest.kt | 40 ++++++++++++++++++- .../StudyServiceRequestsTest.kt | 21 +++++----- .../studies/infrastructure/StudyStatusTest.kt | 3 +- .../@types/carp.studies.core/index.d.ts | 18 ++++++--- .../tests/carp.studies.core.ts | 10 ++--- 13 files changed, 183 insertions(+), 30 deletions(-) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index 80edf27ca..81de262fb 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -14,6 +14,7 @@ import dk.cachet.carp.studies.domain.users.Participant /** * Application service which allows creating and managing studies. */ +@Suppress( "TooManyFunctions" ) // TODO: Perhaps split up participation management from main interface. interface StudyService { /** @@ -74,6 +75,13 @@ interface StudyService */ suspend fun getParticipants( studyId: UUID ): List + /** + * Specify an [invitation], shared with participants once they are invited to the study with the specified [studyId]. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + suspend fun setInvitation( studyId: UUID, invitation: StudyInvitation ): StudyStatus + /** * Specify the study [protocol] to use for the study with the specified [studyId]. * diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 51356af66..216a9c4c2 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -23,6 +23,7 @@ import dk.cachet.carp.studies.domain.users.StudyOwner /** * Implementation of [StudyService] which allows creating and managing studies. */ +@Suppress( "TooManyFunctions" ) // TODO: Perhaps split up participation management from main interface. class StudyServiceHost( private val repository: StudyRepository, private val deploymentService: DeploymentService @@ -140,12 +141,29 @@ class StudyServiceHost( override suspend fun getParticipants( studyId: UUID ): List = repository.getParticipants( studyId ) + /** + * Specify an [invitation], shared with participants once they are invited to the study with the specified [studyId]. + * + * @throws IllegalArgumentException when a study with [studyId] does not exist. + */ + override suspend fun setInvitation( studyId: UUID, invitation: StudyInvitation ): StudyStatus + { + val study: Study? = repository.getById( studyId ) + require( study != null ) + + study.invitation = invitation + repository.update( study ) + + return study.getStatus() + } + /** * Specify the study [protocol] to use for the study with the specified [studyId]. * * @throws IllegalArgumentException when a study with [studyId] does not exist, * when the provided [protocol] snapshot is invalid, * or when the protocol contains errors preventing it from being used in deployments. + * @throws IllegalStateException when the study protocol can no longer be set since the study went 'live'. */ override suspend fun setProtocol( studyId: UUID, protocol: StudyProtocolSnapshot ): StudyStatus { diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt index 8ff42f651..032c1cb74 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/Study.kt @@ -33,13 +33,14 @@ class Study( /** * A description of the study, shared with participants once they are invited to the study. */ - val invitation: StudyInvitation = StudyInvitation.empty(), + invitation: StudyInvitation = StudyInvitation.empty(), val id: UUID = UUID.randomUUID() ) : AggregateRoot() { sealed class Event : Immutable() { data class InternalDescriptionChanged( val name: String, val description: String ) : Event() + data class InvitationChanged( val invitation: StudyInvitation ) : Event() data class ProtocolSnapshotChanged( val protocolSnapshot: StudyProtocolSnapshot? ) : Event() data class StateChanged( val isLive: Boolean ) : Event() data class ParticipationAdded( val participation: DeanonymizedParticipation ) : Event() @@ -81,6 +82,25 @@ class Study( event( Event.InternalDescriptionChanged( name, description ) ) } + val canSetInvitation: Boolean get() = !isLive + + /** + * A description of the study, shared with participants once they are invited to the study. + */ + var invitation: StudyInvitation = invitation + /** + * Set a new description of the study, to be shared with participants once they are invited to the study. + * + * @throws IllegalStateException when the invitation can no longer be changed since the study went 'live'. + */ + set( value ) + { + check( !isLive ) { "Can't change invitation since this study already went live." } + + field = value + event( Event.InvitationChanged( invitation ) ) + } + /** * The date when this study was created. */ @@ -91,8 +111,8 @@ class Study( * Get the status (serializable) of this [Study]. */ fun getStatus(): StudyStatus = - if ( isLive ) StudyStatus.Live( id, name, creationDate, canDeployToParticipants, canSetStudyProtocol ) - else StudyStatus.Configuring( id, name, creationDate, canDeployToParticipants, canSetStudyProtocol, canGoLive ) + if ( isLive ) StudyStatus.Live( id, name, creationDate, canSetInvitation, canSetStudyProtocol, canDeployToParticipants ) + else StudyStatus.Configuring( id, name, creationDate, canSetInvitation, canSetStudyProtocol, canDeployToParticipants, canGoLive ) val canSetStudyProtocol: Boolean get() = !isLive diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt index 687237d09..58787b048 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/domain/StudyStatus.kt @@ -21,13 +21,17 @@ sealed class StudyStatus */ abstract val creationDate: DateTime /** - * Determines whether the study in its current state is ready to be deployed to participants. + * Determines whether the invitation which is shared with participants can be changed for the study. */ - abstract val canDeployToParticipants: Boolean + abstract val canSetInvitation: Boolean /** * Determines whether a study protocol can be set/changed for the study. */ abstract val canSetStudyProtocol: Boolean + /** + * Determines whether the study in its current state is ready to be deployed to participants. + */ + abstract val canDeployToParticipants: Boolean /** @@ -38,8 +42,9 @@ sealed class StudyStatus override val studyId: UUID, override val name: String, override val creationDate: DateTime, - override val canDeployToParticipants: Boolean, + override val canSetInvitation: Boolean, override val canSetStudyProtocol: Boolean, + override val canDeployToParticipants: Boolean, /** * Determines whether a study is fully configured and can 'go live'. */ @@ -55,7 +60,8 @@ sealed class StudyStatus override val studyId: UUID, override val name: String, override val creationDate: DateTime, - override val canDeployToParticipants: Boolean, - override val canSetStudyProtocol: Boolean + override val canSetInvitation: Boolean, + override val canSetStudyProtocol: Boolean, + override val canDeployToParticipants: Boolean ) : StudyStatus() } diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt index 3223777e7..c0447a700 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt @@ -56,6 +56,11 @@ sealed class StudyServiceRequest StudyServiceRequest(), ServiceInvoker> by createServiceInvoker( StudyService::getParticipants, studyId ) + @Serializable + data class SetInvitation( val studyId: UUID, val invitation: StudyInvitation ) : + StudyServiceRequest(), + ServiceInvoker by createServiceInvoker( StudyService::setInvitation, studyId, invitation ) + @Serializable data class SetProtocol( val studyId: UUID, val protocol: StudyProtocolSnapshot ) : StudyServiceRequest(), diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index 09938dcc1..7676402c1 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -26,6 +26,7 @@ class StudyServiceMock( private val getStudiesOverviewResult: List = listOf(), private val addParticipantResult: Participant = Participant( AccountIdentity.fromEmailAddress( "test@test.com" ) ), private val getParticipantsResult: List = listOf(), + private val setInvitationResult: StudyStatus = studyStatus, private val setProtocolResult: StudyStatus = studyStatus, private val goLiveResult: StudyStatus = studyStatus, private val deployParticipantResult: StudyStatus = studyStatus @@ -35,8 +36,9 @@ class StudyServiceMock( { private val studyStatus = StudyStatus.Configuring( UUID.randomUUID(), "Test", DateTime.now(), - canDeployToParticipants = false, + canSetInvitation = true, canSetStudyProtocol = false, + canDeployToParticipants = false, canGoLive = true ) } @@ -83,6 +85,12 @@ class StudyServiceMock( return getParticipantsResult } + override suspend fun setInvitation( studyId: UUID, invitation: StudyInvitation ): StudyStatus + { + trackSuspendCall( StudyService::setInvitation, studyId, invitation ) + return setInvitationResult + } + override suspend fun setProtocol( studyId: UUID, protocol: StudyProtocolSnapshot ): StudyStatus { trackSuspendCall( StudyService::setProtocol, studyId, protocol ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 2f73fcaf2..351b0d197 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -161,11 +161,31 @@ interface StudyServiceTest assertFailsWith { service.getParticipants( unknownId ) } } + @Test + fun setInvitation_succeeds() = runBlockingTest { + val ( service, _ ) = createService() + val status = service.createStudy( StudyOwner(), "Test" ) + + assertTrue( status.canSetInvitation ) + val invitation = StudyInvitation( "Study name" ) + service.setInvitation( status.studyId, invitation ) + val studyDetails = service.getStudyDetails( status.studyId ) + assertEquals( invitation, studyDetails.invitation ) + } + + @Test + fun setInvitation_fails_for_unknown_studyId() = runBlockingTest { + val ( service, _ ) = createService() + val unknownId = UUID.randomUUID() + assertFailsWith { service.setInvitation( unknownId, StudyInvitation.empty() ) } + } + @Test fun setProtocol_succeeds() = runBlockingTest { val ( service, _ ) = createService() var status = service.createStudy( StudyOwner(), "Test" ) + assertTrue( status.canSetStudyProtocol ) val protocol = createDeployableProtocol() status = service.setProtocol( status.studyId, protocol ) assertFalse( status.canDeployToParticipants ) @@ -205,6 +225,21 @@ interface StudyServiceTest assertFailsWith { service.setProtocol( status.studyId, protocol.getSnapshot() ) } } + @Test + fun setInvitation_and_setProtocol_fails_after_study_gone_live() = runBlockingTest { + val ( service, _ ) = createService() + var status = service.createStudy( StudyOwner(), "Test" ) + + val protocol = createDeployableProtocol() + service.setProtocol( status.studyId, protocol ) + status = service.goLive( status.studyId ) + assertFalse( status.canSetInvitation ) + assertFalse( status.canSetStudyProtocol ) + + assertFailsWith { service.setInvitation( status.studyId, StudyInvitation.empty() ) } + assertFailsWith { service.setProtocol( status.studyId, protocol ) } + } + @Test fun goLive_succeeds() = runBlockingTest { val ( service, _ ) = createService() diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt index ce4523499..ee5aec948 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt @@ -71,11 +71,14 @@ interface StudyRepositoryTest study.name = "Changed name" study.description = "Changed description" + val newInvitation = StudyInvitation( "Test name" ) + study.invitation = newInvitation repo.update( study ) val updatedStudy = repo.getById( study.id ) assertNotNull( updatedStudy ) assertEquals( "Changed name", updatedStudy.name ) assertEquals( "Changed description", updatedStudy.description ) + assertEquals( newInvitation, updatedStudy.invitation ) } @Test diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt index 440fb7ea0..9c0b81a5f 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt @@ -2,6 +2,7 @@ package dk.cachet.carp.studies.domain import dk.cachet.carp.common.UUID import dk.cachet.carp.deployment.domain.users.Participation +import dk.cachet.carp.deployment.domain.users.StudyInvitation import dk.cachet.carp.protocols.domain.ProtocolOwner import dk.cachet.carp.protocols.domain.StudyProtocol import dk.cachet.carp.protocols.domain.devices.Smartphone @@ -34,6 +35,28 @@ class StudyTest assertEquals( study.participations, fromSnapshot.participations ) } + @Test + fun set_invitation_succeeds() + { + val study = createStudy() + + assertTrue( study.canSetInvitation ) + + val invitation = StudyInvitation( "Test study" ) + study.invitation = invitation + assertEquals( invitation, study.invitation ) + } + + @Test + fun set_invitation_fails_for_live_study() + { + val study = createStudy() + setDeployableProtocol( study ) + study.goLive() + + assertFailsWith { study.invitation = StudyInvitation.empty() } + } + @Test fun set_protocol_succeeds() { @@ -71,8 +94,9 @@ class StudyTest assertFalse( study.isLive ) val status = study.getStatus() - assertEquals( study.canDeployToParticipants, status.canDeployToParticipants ) + assertEquals( study.canSetInvitation, status.canSetInvitation ) assertEquals( study.canSetStudyProtocol, status.canSetStudyProtocol ) + assertEquals( study.canDeployToParticipants, status.canDeployToParticipants ) assertTrue( status is StudyStatus.Configuring ) assertFalse( status.canGoLive ) } @@ -93,6 +117,20 @@ class StudyTest assertEquals( Study.Event.StateChanged( true ), study.consumeEvents().last() ) } + @Test + fun canSetInvitation_and_canSetStudyProtocol_true_until_live() + { + val study = createStudy() + assertTrue( study.canSetInvitation ) + assertTrue( study.canSetStudyProtocol ) + + // Go live. + setDeployableProtocol( study ) + study.goLive() + assertFalse( study.canSetInvitation ) + assertFalse( study.canSetStudyProtocol ) + } + @Test fun goLive_can_be_called_more_than_once() { diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt index c98c0ef15..103109811 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt @@ -20,17 +20,20 @@ class StudyServiceRequestsTest { companion object { + private val studyId = UUID.randomUUID() + val requests: List = listOf( StudyServiceRequest.CreateStudy( StudyOwner(), "Test", "Description", StudyInvitation.empty() ), - StudyServiceRequest.UpdateInternalDescription( UUID.randomUUID(), "New name", "New description" ), - StudyServiceRequest.GetStudyDetails( UUID.randomUUID() ), - StudyServiceRequest.GetStudyStatus( UUID.randomUUID() ), - StudyServiceRequest.GetStudiesOverview(StudyOwner()), - StudyServiceRequest.AddParticipant( UUID.randomUUID(), EmailAddress( "test@test.com" ) ), - StudyServiceRequest.GetParticipants( UUID.randomUUID() ), - StudyServiceRequest.SetProtocol( UUID.randomUUID(), StudyProtocol( ProtocolOwner(), "Test" ).getSnapshot() ), - StudyServiceRequest.GoLive( UUID.randomUUID() ), - StudyServiceRequest.DeployParticipantGroup( UUID.randomUUID(), setOf() ) + StudyServiceRequest.UpdateInternalDescription( studyId, "New name", "New description" ), + StudyServiceRequest.GetStudyDetails( studyId ), + StudyServiceRequest.GetStudyStatus( studyId ), + StudyServiceRequest.GetStudiesOverview( StudyOwner() ), + StudyServiceRequest.AddParticipant( studyId, EmailAddress( "test@test.com" ) ), + StudyServiceRequest.GetParticipants( studyId ), + StudyServiceRequest.SetInvitation( studyId, StudyInvitation.empty() ), + StudyServiceRequest.SetProtocol( studyId, StudyProtocol( ProtocolOwner(), "Test" ).getSnapshot() ), + StudyServiceRequest.GoLive( studyId ), + StudyServiceRequest.DeployParticipantGroup( studyId, setOf() ) ) } diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt index 9891933fe..df0dd6ac3 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyStatusTest.kt @@ -16,8 +16,9 @@ class StudyStatusTest { val status = StudyStatus.Configuring( UUID.randomUUID(), "Test", DateTime.now(), - canDeployToParticipants = false, + canSetInvitation = false, canSetStudyProtocol = true, + canDeployToParticipants = false, canGoLive = false ) val serialized = status.toJson() diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index 325991a3a..01545fae7 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -52,29 +52,33 @@ declare module 'carp.studies.core' { constructor( studyId: UUID, name: string, creationDate: DateTime, - canDeployToParticipants: boolean, + canSetInvitation: boolean, canSetStudyProtocol: boolean, + canDeployToParticipants: boolean, canGoLive: boolean ) readonly studyId: UUID readonly name: string readonly creationDate: DateTime - readonly canDeployToParticipants: boolean + readonly canSetInvitation: boolean readonly canSetStudyProtocol: boolean + readonly canDeployToParticipants: boolean readonly canGoLive: boolean } class Live { constructor( studyId: UUID, name: string, creationDate: DateTime, - canDeployToParticipants: boolean, - canSetStudyProtocol: boolean ) + canSetInvitation: boolean, + canSetStudyProtocol: boolean, + canDeployToParticipants: boolean ) readonly studyId: UUID readonly name: string readonly creationDate: DateTime - readonly canDeployToParticipants: boolean + readonly canSetInvitation: boolean readonly canSetStudyProtocol: boolean + readonly canDeployToParticipants: boolean } } } @@ -162,6 +166,10 @@ declare module 'carp.studies.core' { constructor( studyId: UUID ) } + class SetInvitation extends StudyServiceRequest + { + constructor( studyId: UUID, invitation: StudyInvitation ) + } class SetProtocol extends StudyServiceRequest { constructor( studyId: UUID, protocol: StudyProtocolSnapshot ) diff --git a/typescript-declarations/tests/carp.studies.core.ts b/typescript-declarations/tests/carp.studies.core.ts index 2a3190501..bfa846e89 100644 --- a/typescript-declarations/tests/carp.studies.core.ts +++ b/typescript-declarations/tests/carp.studies.core.ts @@ -37,8 +37,8 @@ describe( "carp.studies.core", () => { StudyOwner.Companion, new StudyDetails( UUID.Companion.randomUUID(), new StudyOwner(), "Name", DateTime.Companion.now(), "Description", StudyInvitation.Companion.empty(), null ), StudyDetails.Companion, - new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ), - new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), true, false ), + new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), true, true, false, true ), + new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, false, true ), StudyStatus.Companion, StudyServiceRequest.Companion ] @@ -78,12 +78,12 @@ describe( "carp.studies.core", () => { describe( "StudyStatus", () => { it ( "can typecheck StudyStatus", () => { - const configuring = new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ) + const configuring = new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), true, true, false, true ) const configuringStatus: StudyStatus = configuring expect( configuringStatus instanceof StudyStatus.Configuring ).is.true expect( configuringStatus instanceof StudyStatus.Live ).is.false - const live = new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true ) + const live = new StudyStatus.Live( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, false, true ) const liveStatus: StudyStatus = live expect( liveStatus instanceof StudyStatus.Live ).is.true expect( liveStatus instanceof StudyStatus.Configuring ).is.false @@ -121,7 +121,7 @@ describe( "carp.studies.core", () => { } ) it( "can serialize getStudiesOverview response", () => { - const status = new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), false, true, false ) + const status = new StudyStatus.Configuring( UUID.Companion.randomUUID(), "Test", DateTime.Companion.now(), true, true, false, true ) const statusList = new ArrayList( [ status ] ) const json: Json = createStudiesSerializer() From 071770f4ad6f5f53d68ace25e552d8d41cd3333b Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Wed, 25 Mar 2020 17:25:35 +0100 Subject: [PATCH 07/11] Add `description` to `StudyInvitation`. --- .../carp/deployment/domain/users/StudyInvitation.kt | 8 ++++++-- .../deployment/application/DeploymentServiceTest.kt | 2 +- .../deployment/infrastructure/StudyInvitationTest.kt | 2 +- .../carp/studies/application/StudyServiceHost.kt | 2 +- .../carp/studies/application/StudyServiceTest.kt | 10 +++++++--- .../cachet/carp/studies/domain/StudyRepositoryTest.kt | 2 +- .../kotlin/dk/cachet/carp/studies/domain/StudyTest.kt | 2 +- .../@types/carp.deployment.core/index.d.ts | 5 +++-- 8 files changed, 21 insertions(+), 12 deletions(-) diff --git a/carp.deployment.core/src/commonMain/kotlin/dk/cachet/carp/deployment/domain/users/StudyInvitation.kt b/carp.deployment.core/src/commonMain/kotlin/dk/cachet/carp/deployment/domain/users/StudyInvitation.kt index a36b2a4c3..dbdbcfb69 100644 --- a/carp.deployment.core/src/commonMain/kotlin/dk/cachet/carp/deployment/domain/users/StudyInvitation.kt +++ b/carp.deployment.core/src/commonMain/kotlin/dk/cachet/carp/deployment/domain/users/StudyInvitation.kt @@ -11,7 +11,11 @@ data class StudyInvitation( /** * A descriptive name for the study to be shown to participants. */ - val name: String + val name: String, + /** + * A description of the study clarifying to participants what it is about. + */ + val description: String ) { companion object @@ -19,6 +23,6 @@ data class StudyInvitation( /** * Initializes a [StudyInvitation] with blank values for all fields. */ - fun empty(): StudyInvitation = StudyInvitation( "" ) + fun empty(): StudyInvitation = StudyInvitation( "", "" ) } } diff --git a/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/application/DeploymentServiceTest.kt b/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/application/DeploymentServiceTest.kt index 41240aa9a..feafd3a17 100644 --- a/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/application/DeploymentServiceTest.kt +++ b/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/application/DeploymentServiceTest.kt @@ -75,7 +75,7 @@ abstract class DeploymentServiceTest val invitation = StudyInvitation.empty() deploymentService.addParticipation( studyDeploymentId, setOf( deviceRoleName ), emailIdentity, invitation ) - val differentInvitation = StudyInvitation( "Different" ) + val differentInvitation = StudyInvitation( "Different", "New description" ) assertFailsWith { deploymentService.addParticipation( studyDeploymentId, setOf( deviceRoleName ), emailIdentity, differentInvitation ) diff --git a/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/infrastructure/StudyInvitationTest.kt b/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/infrastructure/StudyInvitationTest.kt index bc4eaa3aa..8afc64d6b 100644 --- a/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/infrastructure/StudyInvitationTest.kt +++ b/carp.deployment.core/src/commonTest/kotlin/dk/cachet/carp/deployment/infrastructure/StudyInvitationTest.kt @@ -12,7 +12,7 @@ class StudyInvitationTest @Test fun can_serialize_and_deserialize_study_description_using_JSON() { - val invitation = StudyInvitation( "Test" ) + val invitation = StudyInvitation( "Test", "Description" ) val serialized = invitation.toJson() val parsed = StudyInvitation.fromJson( serialized ) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 216a9c4c2..2d2e7781e 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -40,7 +40,7 @@ class StudyServiceHost( */ override suspend fun createStudy( owner: StudyOwner, name: String, description: String, invitation: StudyInvitation? ): StudyStatus { - val ensuredInvitation = invitation ?: StudyInvitation( name ) + val ensuredInvitation = invitation ?: StudyInvitation( name, "" ) val study = Study( owner, name, description, ensuredInvitation ) repository.add( study ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 351b0d197..12faa89d5 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -39,7 +39,11 @@ interface StudyServiceTest assertNotNull( foundStudy ) assertEquals( status.studyId, foundStudy.id ) assertEquals( name, foundStudy.name ) - assertEquals( name, foundStudy.invitation.name ) // Default study description when not specified. + + // Default study description when not specified. + assertEquals( name, foundStudy.invitation.name ) + assertEquals( "", foundStudy.invitation.description ) + assertFalse( foundStudy.canDeployToParticipants ) } @@ -50,7 +54,7 @@ interface StudyServiceTest val owner = StudyOwner() val name = "Test" val description = "Description" - val invitation = StudyInvitation( "Lorem ipsum" ) + val invitation = StudyInvitation( "Lorem ipsum", "Some description" ) val status = service.createStudy( owner, name, description, invitation ) val foundStudy = repo.getById( status.studyId )!! @@ -167,7 +171,7 @@ interface StudyServiceTest val status = service.createStudy( StudyOwner(), "Test" ) assertTrue( status.canSetInvitation ) - val invitation = StudyInvitation( "Study name" ) + val invitation = StudyInvitation( "Study name", "Description" ) service.setInvitation( status.studyId, invitation ) val studyDetails = service.getStudyDetails( status.studyId ) assertEquals( invitation, studyDetails.invitation ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt index ee5aec948..ee7f6c16d 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyRepositoryTest.kt @@ -71,7 +71,7 @@ interface StudyRepositoryTest study.name = "Changed name" study.description = "Changed description" - val newInvitation = StudyInvitation( "Test name" ) + val newInvitation = StudyInvitation( "Test name", "Test description" ) study.invitation = newInvitation repo.update( study ) val updatedStudy = repo.getById( study.id ) diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt index 9c0b81a5f..d593044b1 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/domain/StudyTest.kt @@ -42,7 +42,7 @@ class StudyTest assertTrue( study.canSetInvitation ) - val invitation = StudyInvitation( "Test study" ) + val invitation = StudyInvitation( "Test study", "This is a test." ) study.invitation = invitation assertEquals( invitation, study.invitation ) } diff --git a/typescript-declarations/@types/carp.deployment.core/index.d.ts b/typescript-declarations/@types/carp.deployment.core/index.d.ts index b51159c20..27bd825ba 100644 --- a/typescript-declarations/@types/carp.deployment.core/index.d.ts +++ b/typescript-declarations/@types/carp.deployment.core/index.d.ts @@ -8,11 +8,12 @@ declare module 'carp.deployment.core' { class StudyInvitation { - constructor( name: string ) + constructor( name: string, description: string ) static get Companion(): StudyInvitation$Companion - readonly name: UUID + readonly name: string + readonly description: string } interface StudyInvitation$Companion { From 634ebda8ef5a94338707969644e53fa064e0e2e5 Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Wed, 25 Mar 2020 17:33:29 +0100 Subject: [PATCH 08/11] Refactor: rename `updateInternalDescription` to `set ...` --- .../dk/cachet/carp/studies/application/StudyService.kt | 4 ++-- .../cachet/carp/studies/application/StudyServiceHost.kt | 4 ++-- .../carp/studies/infrastructure/StudyServiceRequest.kt | 4 ++-- .../cachet/carp/studies/application/StudyServiceMock.kt | 4 ++-- .../cachet/carp/studies/application/StudyServiceTest.kt | 8 ++++---- .../studies/infrastructure/StudyServiceRequestsTest.kt | 2 +- .../@types/carp.studies.core/index.d.ts | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index 81de262fb..b735d72ad 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -29,7 +29,7 @@ interface StudyService suspend fun createStudy( owner: StudyOwner, name: String, description: String = "", invitation: StudyInvitation? = null ): StudyStatus /** - * Update study details which are visible only to the [StudyOwner]. + * Set study details which are visible only to the [StudyOwner]. * * @param studyId The id of the study to update the study details for. * @param name A descriptive name for the study. @@ -37,7 +37,7 @@ interface StudyService * * @throws IllegalArgumentException when a study with [studyId] does not exist. */ - suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus + suspend fun setInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus /** * Gets detailed information about the study with the specified [studyId], including which study protocol is set. diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index 2d2e7781e..d8aafa8be 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -49,7 +49,7 @@ class StudyServiceHost( } /** - * Update study details which are visible only to the [StudyOwner]. + * Set study details which are visible only to the [StudyOwner]. * * @param studyId The id of the study to update the study details for. * @param name A descriptive name for the study. @@ -57,7 +57,7 @@ class StudyServiceHost( * * @throws IllegalArgumentException when a study with [studyId] does not exist. */ - override suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus + override suspend fun setInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus { val study = repository.getById( studyId ) require( study != null ) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt index c0447a700..4dc5e52a4 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequest.kt @@ -27,9 +27,9 @@ sealed class StudyServiceRequest ServiceInvoker by createServiceInvoker( StudyService::createStudy, owner, name, description, invitation ) @Serializable - data class UpdateInternalDescription( val studyId: UUID, val name: String, val description: String ) : + data class SetInternalDescription( val studyId: UUID, val name: String, val description: String ) : StudyServiceRequest(), - ServiceInvoker by createServiceInvoker( StudyService::updateInternalDescription, studyId, name, description ) + ServiceInvoker by createServiceInvoker( StudyService::setInternalDescription, studyId, name, description ) @Serializable data class GetStudyDetails( val studyId: UUID ) : diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt index 7676402c1..80f503d39 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceMock.kt @@ -49,9 +49,9 @@ class StudyServiceMock( return createStudyResult } - override suspend fun updateInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus + override suspend fun setInternalDescription( studyId: UUID, name: String, description: String ): StudyStatus { - trackSuspendCall( StudyService::updateInternalDescription, studyId, name, description ) + trackSuspendCall( StudyService::setInternalDescription, studyId, name, description ) return updateInternalDescriptionResult } diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt index 12faa89d5..39ceb59e2 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/application/StudyServiceTest.kt @@ -66,13 +66,13 @@ interface StudyServiceTest } @Test - fun updateInternalDescription_succeeds() = runBlockingTest { + fun setInternalDescription_succeeds() = runBlockingTest { val ( service, _ ) = createService() val status = service.createStudy( StudyOwner(), "Test" ) val newName = "New name" val newDescription = "New description" - val updatedStatus = service.updateInternalDescription( status.studyId, newName, newDescription ) + val updatedStatus = service.setInternalDescription( status.studyId, newName, newDescription ) assertEquals( newName, updatedStatus.name ) val studyDetails = service.getStudyDetails( status.studyId ) assertEquals( newName, studyDetails.name ) @@ -80,10 +80,10 @@ interface StudyServiceTest } @Test - fun updateInternalDescription_fails_for_unknown_studyId() = runBlockingTest { + fun setInternalDescription_fails_for_unknown_studyId() = runBlockingTest { val ( service, _ ) = createService() - assertFailsWith { service.updateInternalDescription( UUID.randomUUID(), "New name", "New description" ) } + assertFailsWith { service.setInternalDescription( UUID.randomUUID(), "New name", "New description" ) } } @Test diff --git a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt index 103109811..19b965244 100644 --- a/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt +++ b/carp.studies.core/src/commonTest/kotlin/dk/cachet/carp/studies/infrastructure/StudyServiceRequestsTest.kt @@ -24,7 +24,7 @@ class StudyServiceRequestsTest val requests: List = listOf( StudyServiceRequest.CreateStudy( StudyOwner(), "Test", "Description", StudyInvitation.empty() ), - StudyServiceRequest.UpdateInternalDescription( studyId, "New name", "New description" ), + StudyServiceRequest.SetInternalDescription( studyId, "New name", "New description" ), StudyServiceRequest.GetStudyDetails( studyId ), StudyServiceRequest.GetStudyStatus( studyId ), StudyServiceRequest.GetStudiesOverview( StudyOwner() ), diff --git a/typescript-declarations/@types/carp.studies.core/index.d.ts b/typescript-declarations/@types/carp.studies.core/index.d.ts index 01545fae7..e9ae10e59 100644 --- a/typescript-declarations/@types/carp.studies.core/index.d.ts +++ b/typescript-declarations/@types/carp.studies.core/index.d.ts @@ -142,7 +142,7 @@ declare module 'carp.studies.core' { constructor( owner: StudyOwner, name: string, description: string, invitation: StudyInvitation ) } - class UpdateInternalDescription extends StudyServiceRequest + class SetInternalDescription extends StudyServiceRequest { constructor( studyId: UUID, name: string, description: string ) } From 18fc0de9a8d1f1f4a21ee66c29fc463d896da699 Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Wed, 25 Mar 2020 18:04:36 +0100 Subject: [PATCH 09/11] Update all dependency versions (except dokka), including Kotlin 1.3.71. --- build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index beb3cfbe1..6aacd0fa7 100644 --- a/build.gradle +++ b/build.gradle @@ -13,22 +13,22 @@ buildscript { versions = [ // Kotlin multiplatform versions. - kotlin:'1.3.70', + kotlin:'1.3.71', serialization:'0.20.0', - coroutines:'1.3.4', + coroutines:'1.3.5', // JVM versions. jvmTarget:'1.6', - jUnit5:'5.6.0', + jUnit5:'5.6.1', dokkaPlugin:'0.9.18', // JS versions. nodePlugin:'2.2.3', - node:'13.10.1', - mocha:'7.1.0', + node:'13.11.0', + mocha:'7.1.1', // DevOps versions. - detektPlugin:'1.7.0-beta1', + detektPlugin:'1.7.0', nexusReleasePlugin:'0.21.2' ] } From 8e2caa8c2d527a44e71976b61dba754d9317fc23 Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Thu, 26 Mar 2020 14:13:43 +0100 Subject: [PATCH 10/11] Refactor: max line length for `StudyService.createStudy`. --- .../carp/studies/application/StudyService.kt | 23 +++++++++++++------ .../studies/application/StudyServiceHost.kt | 23 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt index b735d72ad..e32b0e01c 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyService.kt @@ -19,14 +19,23 @@ interface StudyService { /** * Create a new study for the specified [owner]. - * - * @param name A descriptive name for the study, assigned by, and only visible to, the [owner]. - * @param description An optional description of the study, assigned by, and only visible to, the [owner]. - * @param invitation - * An optional description of the study, shared with participants once they are invited. - * In case no description is specified, [name] is used as the name in [invitation]. */ - suspend fun createStudy( owner: StudyOwner, name: String, description: String = "", invitation: StudyInvitation? = null ): StudyStatus + suspend fun createStudy( + owner: StudyOwner, + /** + * A descriptive name for the study, assigned by, and only visible to, the [owner]. + */ + name: String, + /** + * An optional description of the study, assigned by, and only visible to, the [owner]. + */ + description: String = "", + /** + * An optional description of the study, shared with participants once they are invited. + * In case no description is specified, [name] is used as the name in [invitation]. + */ + invitation: StudyInvitation? = null + ): StudyStatus /** * Set study details which are visible only to the [StudyOwner]. diff --git a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt index d8aafa8be..6566db99f 100644 --- a/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt +++ b/carp.studies.core/src/commonMain/kotlin/dk/cachet/carp/studies/application/StudyServiceHost.kt @@ -31,14 +31,23 @@ class StudyServiceHost( { /** * Create a new study for the specified [owner]. - * - * @param name A descriptive name for the study, assigned by, and only visible to, the [owner]. - * @param description An optional description of the study, assigned by, and only visible to, the [owner]. - * @param invitation - * An optional description of the study, shared with participants once they are invited. - * In case no description is specified, [name] is used as the name in [invitation]. */ - override suspend fun createStudy( owner: StudyOwner, name: String, description: String, invitation: StudyInvitation? ): StudyStatus + override suspend fun createStudy( + owner: StudyOwner, + /** + * A descriptive name for the study, assigned by, and only visible to, the [owner]. + */ + name: String, + /** + * An optional description of the study, assigned by, and only visible to, the [owner]. + */ + description: String, + /** + * An optional description of the study, shared with participants once they are invited. + * In case no description is specified, [name] is used as the name in [invitation]. + */ + invitation: StudyInvitation? + ): StudyStatus { val ensuredInvitation = invitation ?: StudyInvitation( name, "" ) val study = Study( owner, name, description, ensuredInvitation ) From d9238cc249a223eb6dbf8a0908336d65e9019882 Mon Sep 17 00:00:00 2001 From: Steven Jeuris Date: Thu, 26 Mar 2020 14:22:04 +0100 Subject: [PATCH 11/11] Bump to version 1.0.0-alpha.14 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6aacd0fa7..67700b7ba 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { // Version used for all submodule artifacts. // Snapshot publishing changes (or adds) the suffix after '-' with 'SNAPSHOT' prior to publishing. // The 'publishSigned' task publishes to SonaType's staging repo and 'publishSnapshot' instantly uploads to the snapshots repo. - globalVersion = '1.0.0-alpha.13' + globalVersion = '1.0.0-alpha.14' versions = [ // Kotlin multiplatform versions.