Skip to content

Commit

Permalink
integration tests for build approval and service message notifications
Browse files Browse the repository at this point in the history
Merge-request: TC-MR-4737
  • Loading branch information
Fedor Rumyantsev authored and qodana-bot committed Apr 11, 2023
1 parent 69b42c9 commit 4397f69
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@

package jetbrains.buildServer.notification.slackNotifier

import jetbrains.buildServer.BaseTestCase
import jetbrains.buildServer.BuildProblemData
import jetbrains.buildServer.ExtensionHolder
import jetbrains.buildServer.buildFeatures.approvals.ApprovalConstants
import jetbrains.buildServer.messages.DefaultMessagesInfo
import jetbrains.buildServer.messages.Status
import jetbrains.buildServer.notification.*
import jetbrains.buildServer.notification.slackNotifier.notification.*
import jetbrains.buildServer.notification.slackNotifier.slack.*
import jetbrains.buildServer.serverSide.*
import jetbrains.buildServer.serverSide.impl.NotificationRulesConstants
import jetbrains.buildServer.serverSide.impl.approval.ApprovalBuildFeatureConfiguration
import jetbrains.buildServer.serverSide.oauth.OAuthConnectionDescriptor
import jetbrains.buildServer.serverSide.oauth.OAuthConnectionsManager
import jetbrains.buildServer.users.SUser
Expand All @@ -39,6 +42,7 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
private lateinit var mySlackApi: StoringMessagesSlackWebApi
protected lateinit var myDescriptor: SlackNotifierDescriptor
private lateinit var myNotifier: SlackNotifier
protected lateinit var myChannelName: String
private lateinit var myUser: SUser
private lateinit var myAssignerUser: SUser
private lateinit var myAdHocMessageBuilder: PlainAdHocMessageBuilder
Expand Down Expand Up @@ -95,18 +99,11 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
myDescriptor = SlackNotifierDescriptor(myFixture.getSingletonService(NotificatorRegistry::class.java))
myNotifier = createNotifier()

myConnection = myConnectionManager.addConnection(
myProject,
SlackConnection.type,
mapOf(
"secure:token" to "test_token",
"clientId" to "test_clientId",
"secure:clientSecret" to "test_clientSecret"
)
)
myConnection = addConnection()

myUser = createUser("test_user")
myUser.setUserProperty(SlackProperties.channelProperty, "#test_channel")
myChannelName = "#test_channel"
myUser.setUserProperty(SlackProperties.channelProperty, myChannelName)
myUser.setUserProperty(SlackProperties.connectionProperty, myConnection.id)
makeProjectAccessible(myUser, myProject.projectId)

Expand Down Expand Up @@ -142,6 +139,23 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
return notifier
}

private fun addConnection(
maxNotificationsPerBuild: Int = 0,
allowedDomainNames: Set<String> = setOf()
): OAuthConnectionDescriptor {
return myConnectionManager.addConnection(
myProject,
SlackConnection.type,
mapOf(
"secure:token" to "test_token",
"clientId" to "test_clientId",
"secure:clientSecret" to "test_clientSecret",
"adHocMaxNotificationsPerBuild" to maxNotificationsPerBuild.toString(),
"adHocAllowedDomainNames" to allowedDomainNames.joinToString(",")
)
)
}

fun `given user is subscribed to`(vararg events: NotificationRule.Event) {
storeRules(myUser, myNotifier, newRule(*events))
}
Expand Down Expand Up @@ -216,6 +230,10 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
))
}

fun `given there is connection with enabled service message notifications`() {

}

private fun addBuildFeature(
vararg events: NotificationRule.Event,
additionalParameters: Map<String, String> = emptyMap(),
Expand Down Expand Up @@ -262,14 +280,40 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
initHangingSlackWebApi("conversationsMembers")
}

fun `given there is connection allowing service message notifications`(limit: Int) {
addConnection(maxNotificationsPerBuild = limit)
}

fun `given there are more multiple connections allowing service message notifications`() {
addConnection(maxNotificationsPerBuild = 1)
addConnection(maxNotificationsPerBuild = 1)
}

fun `given there is an allowed external domain`(limit: Int, allowedDomainNames: Set<String>) {
addConnection(maxNotificationsPerBuild = limit, allowedDomainNames = allowedDomainNames)
}

private fun initHangingSlackWebApi(methodThatAreHanging: String) {
mySlackApiFactory =
HangingSlackWebApiFactoryStub(methodThatAreHanging, mySlackApi, myFixture.executorServices)
mySlackApi = mySlackApiFactory.createSlackWebApi()
}

fun `when approvable build is queued`(): SQueuedBuild {
myBuildType.addBuildFeature(
ApprovalConstants.FEATURE_TYPE,
mapOf(
ApprovalConstants.FEATURE_SETTING_RULES to "user:${myUser.username}",
ApprovalConstants.FEATURE_SETTING_TIMEOUT to ApprovalBuildFeatureConfiguration.DEFAULT_TIMEOUT_MINUTES.toString(),
ApprovalConstants.FEATURE_SETTING_MANUAL_START_IS_APPROVAL to true.toString()
)
)

return addToQueue(myBuildType)
}

fun `when build starts`(): SBuild = startBuild()

fun `when build is triggered manually`(): SBuild {
return build().`in`(myBuildType).by(createUser("user_who_triggered_the_build")).run()
}
Expand Down Expand Up @@ -427,6 +471,33 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
return myUser
}

fun `when service message notification is sent`(
message: String = "service message",
sendTo: String = myChannelName
) {
startBuild()
myFixture.logBuildMessages(
runningBuild,
listOf(DefaultMessagesInfo.createTextMessage("##teamcity[notification message='$message' notifier='slack' sendTo='$sendTo']"))
)
runningBuild.updateBuild()
}

fun `when multiple service message notifications are sent`(
message: String = "service message",
sendTo: String = myChannelName,
count: Int = 2
) {
startBuild()
for (i in 0..count) {
myFixture.logBuildMessages(
runningBuild,
listOf(DefaultMessagesInfo.createTextMessage("##teamcity[notification message='$message' notifier='slack' sensTo='$sendTo']"))
)
runningBuild.updateBuild()
}
}

fun `then message should contain`(vararg strings: String) {
waitForMessage()
for (str in strings) {
Expand Down Expand Up @@ -472,6 +543,18 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
assertTrue(mySlackApi.messages.last().text!!.length < 1000)
}

fun `then exception is logged in the build log`(exceptionMessage: String) {
val prefix = "Service message notification exception: "
val errorMessages = runningBuild.buildLog.errorMessages
val exceptions = errorMessages
.filter {it.text.contains(prefix)}
.map { it.text }
assertEquals(
listOf(prefix + exceptionMessage),
exceptions
)
}

private fun waitForMessage() {
waitForAssert(BooleanSupplier {
mySlackApi.messages.isNotEmpty()
Expand All @@ -482,7 +565,6 @@ open class BaseSlackTestCase : BaseNotificationRulesTestCase() {
assertEmpty(mySlackApi.messages)
}


fun Int.`messages should be sent`() {
waitForAssert(BooleanSupplier {
mySlackApi.messages.size == this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package jetbrains.buildServer.notification.slackNotifier.notification


import jetbrains.buildServer.notification.AdHocNotificationException
import jetbrains.buildServer.notification.NotificationRule.Event.*
import jetbrains.buildServer.notification.slackNotifier.And
import jetbrains.buildServer.notification.slackNotifier.BaseSlackTestCase
Expand Down Expand Up @@ -81,6 +82,14 @@ class SlackNotifierTest : BaseSlackTestCase() {
1.`messages should be sent`()
}

@Test
fun `should send notification about build that requires approval`() {
`given user is subscribed to`(QUEUED_BUILD_REQUIRES_APPROVAL)
`when approvable build is queued`()
`then message should contain`("is waiting for approval") And
1.`messages should be sent`()
}

@Test
fun `build feature should send notification about build start`() {
`given build feature is subscribed to`(BUILD_STARTED)
Expand Down Expand Up @@ -124,6 +133,14 @@ class SlackNotifierTest : BaseSlackTestCase() {
`then message should contain`("fail", build.buildNumber)
}

@Test
fun `build feature should send notification about build that requires approval`() {
`given build feature is subscribed to`(QUEUED_BUILD_REQUIRES_APPROVAL)
`when approvable build is queued`()
`then message should contain`("is waiting for approval") And
1.`messages should be sent`()
}

@Test
fun `notification about manual build start should contain user who triggered it`() {
`given user is subscribed to`(BUILD_STARTED)
Expand Down Expand Up @@ -236,4 +253,45 @@ class SlackNotifierTest : BaseSlackTestCase() {
`when build finishes`()
`then message should contain`("success")
}

@Test
fun `service message notification should fail if no connections allow it`() {
`when service message notification is sent`()
`then exception is logged in the build log`("Could not find any suitable Slack connection with service message notifications enabled")
}

@Test
fun `service message notification should be sent if exactly one connection allows it`() {
`given there is connection allowing service message notifications`(1)
`when service message notification is sent`()
`then message should contain`("service message")
}

@Test
fun `service message notification should fail if more than one connection allow it`() {
`given there are more multiple connections allowing service message notifications`()
`when service message notification is sent`()
`then exception is logged in the build log`("More than one suitable Slack connection was found, please specify 'connectionId' argument to explicitly select connection")
}

@Test
fun `service message notification should fail if notification limit is reached`() {
`given there is connection allowing service message notifications`(1)
`when multiple service message notifications are sent`(count = 2)
`then exception is logged in the build log`("Reached limit of 1 ad-hoc Slack notifications per build")
}

@Test
fun `service message notification should fail if there are external domains not in whitelist`() {
`given there is connection allowing service message notifications`(1)
`when service message notification is sent`(message = "link to externaldomain.com")
`then exception is logged in the build log`("Found external domains that are not allowed by configuration: [externaldomain.com]")
}

@Test
fun `service message notification should be sent if there are whitelisted external domains`() {
`given there is an allowed external domain`(1, setOf("externaldomain.com"))
`when service message notification is sent`(message = "link to externaldomain.com")
`then message should contain`("externaldomain.com")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ class SlackNotifier(
return descriptors.first()
}
descriptors.isEmpty() -> {
throw AdHocNotificationException("Could not find any suitable Slack connection with ad-hoc notifications enabled")
throw AdHocNotificationException("Could not find any suitable Slack connection with service message notifications enabled")
}
else -> {
throw AdHocNotificationException("More than one suitable Slack connection was found, please specify 'connectionId' argument to explicitly select connection")
Expand Down

0 comments on commit 4397f69

Please sign in to comment.