[Build, IGS] Add a consent details document link

Related to KTI-1223
This commit is contained in:
Alexander.Likhachev
2023-05-31 14:40:06 +02:00
committed by Space Team
parent 063e7258ee
commit 04fcffa664
6 changed files with 68 additions and 7 deletions
@@ -11,8 +11,12 @@ import java.io.PrintStream
private val isInIdeaSync private val isInIdeaSync
get() = System.getProperty("idea.sync.active").toBoolean() get() = System.getProperty("idea.sync.active").toBoolean()
private const val linkPlaceholder = "%LINK%"
internal const val USER_CONSENT_MARKER = "# This line indicates that you have chosen to enable automatic configuration of properties." internal const val USER_CONSENT_MARKER = "# This line indicates that you have chosen to enable automatic configuration of properties."
internal const val USER_CONSENT_MARKER_WITH_DETAILS_LINK = "$USER_CONSENT_MARKER Details: $linkPlaceholder"
internal const val USER_REFUSAL_MARKER = internal const val USER_REFUSAL_MARKER =
"# This line indicates that you have chosen to disable automatic configuration of properties. If you want to enable it, remove this line." "# This line indicates that you have chosen to disable automatic configuration of properties. If you want to enable it, remove this line."
@@ -24,8 +28,12 @@ internal val USER_CONSENT_REQUEST = """
""".trimIndent() """.trimIndent()
internal val USER_CONSENT_DETAILS_LINK_TEMPLATE = "You can read more details here: $linkPlaceholder"
internal const val PROMPT_REQUEST = "Do you agree with this? Please answer with 'yes' or 'no': " internal const val PROMPT_REQUEST = "Do you agree with this? Please answer with 'yes' or 'no': "
internal fun String.formatWithLink(link: String) = replace(linkPlaceholder, link)
internal class ConsentManager( internal class ConsentManager(
private val modifier: LocalPropertiesModifier, private val modifier: LocalPropertiesModifier,
private val input: BufferedReader = System.`in`.bufferedReader(), private val input: BufferedReader = System.`in`.bufferedReader(),
@@ -38,14 +46,23 @@ internal class ConsentManager(
else -> null else -> null
} }
fun askForConsent(): Boolean { fun askForConsent(consentDetailsLink: String? = null): Boolean {
output.println(USER_CONSENT_REQUEST) output.println(USER_CONSENT_REQUEST)
if (consentDetailsLink != null) {
output.println(USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink))
}
while (true) { while (true) {
output.println(PROMPT_REQUEST) output.println(PROMPT_REQUEST)
when (input.readLine()) { when (input.readLine()) {
"yes" -> { "yes" -> {
output.println("You've given the consent") output.println("You've given the consent")
modifier.putLine(USER_CONSENT_MARKER) modifier.putLine(
if (consentDetailsLink != null) {
USER_CONSENT_MARKER_WITH_DETAILS_LINK.formatWithLink(consentDetailsLink)
} else {
USER_CONSENT_MARKER
}
)
return true return true
} }
"no" -> { "no" -> {
@@ -23,7 +23,7 @@ abstract class InternalGradleSetupSettingsPlugin : Plugin<Settings> {
private val log = Logging.getLogger(javaClass) private val log = Logging.getLogger(javaClass)
override fun apply(target: Settings) { override fun apply(target: Settings) {
// `kotlin-build-gradle-plugin` is not used here intentionally, as it caches properties, we don't want to cache them before modificaation // `kotlin-build-gradle-plugin` is not used here intentionally, as it caches properties, we don't want to cache them before modification
val shouldApplyPlugin = target.providers.gradleProperty(PLUGIN_SWITCH_PROPERTY).orElse("false").map(String::toBoolean) val shouldApplyPlugin = target.providers.gradleProperty(PLUGIN_SWITCH_PROPERTY).orElse("false").map(String::toBoolean)
if (!shouldApplyPlugin.get()) return // the plugin is disabled, do nothing at all if (!shouldApplyPlugin.get()) return // the plugin is disabled, do nothing at all
val isTeamCityBuild = (target as? ExtensionAware)?.extra?.has("teamcity") == true || System.getenv("TEAMCITY_VERSION") != null val isTeamCityBuild = (target as? ExtensionAware)?.extra?.has("teamcity") == true || System.getenv("TEAMCITY_VERSION") != null
@@ -43,7 +43,7 @@ abstract class InternalGradleSetupSettingsPlugin : Plugin<Settings> {
val setupFile = connection.getInputStream().buffered().use { val setupFile = connection.getInputStream().buffered().use {
parseSetupFile(it) parseSetupFile(it)
} }
if (initialDecision == null && !consentManager.askForConsent()) { if (initialDecision == null && !consentManager.askForConsent(setupFile.consentDetailsLink)) {
log.debug("Skipping automatic local.properties configuration as the consent wasn't given") log.debug("Skipping automatic local.properties configuration as the consent wasn't given")
return return
} }
@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.build package org.jetbrains.kotlin.build
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStream import java.io.InputStream
import java.io.InputStreamReader import java.io.InputStreamReader
@@ -16,9 +18,14 @@ import kotlin.streams.asSequence
@Serializable @Serializable
internal data class SetupFile( internal data class SetupFile(
val properties: Map<String, String>, val properties: Map<String, String>,
val consentDetailsLink: String? = null,
) )
private val json = Json { ignoreUnknownKeys = true } @OptIn(ExperimentalSerializationApi::class)
private val json = Json {
ignoreUnknownKeys = true
namingStrategy = JsonNamingStrategy.SnakeCase
}
// can't use decodeFromStream: https://github.com/Kotlin/kotlinx.serialization/issues/2218 // can't use decodeFromStream: https://github.com/Kotlin/kotlinx.serialization/issues/2218
internal fun parseSetupFile(inputStream: InputStream): SetupFile = internal fun parseSetupFile(inputStream: InputStream): SetupFile =
@@ -52,24 +52,42 @@ class ConsentManagerTest {
@Test @Test
fun testConsentRequestAgree() = testUserDecision("yes", true, USER_CONSENT_MARKER) fun testConsentRequestAgree() = testUserDecision("yes", true, USER_CONSENT_MARKER)
@Test
fun testConsentRequestAgreeWithDetailsLink() = testUserDecision(
"yes",
true,
USER_CONSENT_MARKER_WITH_DETAILS_LINK.formatWithLink("https://example.org"),
consentDetailsLink = "https://example.org"
)
@Test @Test
fun testConsentRequestRefusal() = testUserDecision("no", false, USER_REFUSAL_MARKER) fun testConsentRequestRefusal() = testUserDecision("no", false, USER_REFUSAL_MARKER)
@Test @Test
fun testConsentRequestAnswerAfterBadInput() = testUserDecision("\nasdasd\nno", false, USER_REFUSAL_MARKER, 3) fun testConsentRequestAnswerAfterBadInput() = testUserDecision("\nasdasd\nno", false, USER_REFUSAL_MARKER, 3)
private fun testUserDecision(prompt: String, expectedDecision: Boolean, expectedLine: String, promptCount: Int = 1) { private fun testUserDecision(
prompt: String,
expectedDecision: Boolean,
expectedLine: String,
promptCount: Int = 1,
consentDetailsLink: String? = null,
) {
StringInputStream(prompt).bufferedReader().use { input -> StringInputStream(prompt).bufferedReader().use { input ->
val outputStream = ByteArrayOutputStream(255) val outputStream = ByteArrayOutputStream(255)
PrintStream(outputStream).use { printStream -> PrintStream(outputStream).use { printStream ->
val consentManager = ConsentManager(modifier, input, printStream) val consentManager = ConsentManager(modifier, input, printStream)
val userDecision = consentManager.askForConsent() val userDecision = consentManager.askForConsent(consentDetailsLink)
assertEquals(expectedDecision, userDecision) assertEquals(expectedDecision, userDecision)
val content = Files.readAllLines(localPropertiesFile) val content = Files.readAllLines(localPropertiesFile)
assertTrue(content.contains(expectedLine)) assertTrue(content.contains(expectedLine))
val output = String(outputStream.toByteArray()) val output = String(outputStream.toByteArray())
assertContainsExactTimes(output, USER_CONSENT_REQUEST, 1) assertContainsExactTimes(output, USER_CONSENT_REQUEST, 1)
assertContainsExactTimes(output, PROMPT_REQUEST, promptCount) assertContainsExactTimes(output, PROMPT_REQUEST, promptCount)
if (consentDetailsLink != null) {
assertContainsExactTimes(output, USER_CONSENT_DETAILS_LINK_TEMPLATE.formatWithLink(consentDetailsLink), 1)
}
} }
} }
} }
@@ -29,6 +29,7 @@ class SetupFileParseTest {
parseSetupFile(it) parseSetupFile(it)
} }
assertSampleSetupFileIsParsedCorrectly(setupFile) assertSampleSetupFileIsParsedCorrectly(setupFile)
assertNull(setupFile.consentDetailsLink)
} }
@Test @Test
@@ -37,5 +38,15 @@ class SetupFileParseTest {
parseSetupFile(it) parseSetupFile(it)
} }
assertSampleSetupFileIsParsedCorrectly(setupFile) assertSampleSetupFileIsParsedCorrectly(setupFile)
assertNull(setupFile.consentDetailsLink)
}
@Test
fun testParsingWithConsentDetailsLink() {
val setupFile = openPropertiesJsonStream("properties-with-consent-details").use {
parseSetupFile(it)
}
assertSampleSetupFileIsParsedCorrectly(setupFile)
assertEquals(setupFile.consentDetailsLink, "https://example.org")
} }
} }
@@ -0,0 +1,8 @@
{
"properties": {
"newProperty1": "someValue",
"newProperty2": "someOtherValue",
"alreadySetProperty": "newValue"
},
"consent_details_link": "https://example.org"
}