Skip to content

Commit f5b3269

Browse files
committed
test(apps): cover who an app-made change is recorded against
1 parent 8be4467 commit f5b3269

1 file changed

Lines changed: 157 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package io.tolgee.api.v2.controllers.apps
2+
3+
import io.tolgee.development.testDataBuilder.data.AppsTestData
4+
import io.tolgee.fixtures.andAssertThatJson
5+
import io.tolgee.fixtures.andIsOk
6+
import io.tolgee.fixtures.node
7+
import io.tolgee.service.apps.AppManifestHttpClient
8+
import io.tolgee.service.apps.AppsTestFixtures
9+
import io.tolgee.testing.AuthorizedControllerTest
10+
import org.junit.jupiter.api.AfterEach
11+
import org.junit.jupiter.api.BeforeEach
12+
import org.junit.jupiter.api.Test
13+
import org.springframework.beans.factory.annotation.Autowired
14+
import org.springframework.http.HttpHeaders
15+
import org.springframework.http.MediaType
16+
import org.springframework.test.context.bean.override.mockito.MockitoBean
17+
import org.springframework.test.web.servlet.ResultActions
18+
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
19+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
20+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
21+
22+
/**
23+
* An app writing on its own behalf must not be recorded as the person who registered it: they did
24+
* not make the change, and they may not even work here any more.
25+
*/
26+
class AppActivityAttributionTest : AuthorizedControllerTest() {
27+
@MockitoBean
28+
@Autowired
29+
lateinit var appManifestHttpClient: AppManifestHttpClient
30+
31+
lateinit var testData: AppsTestData
32+
var installId: Long = 0
33+
lateinit var installToken: String
34+
35+
@BeforeEach
36+
fun setup() {
37+
testData = AppsTestData()
38+
testDataService.saveTestData(testData.root)
39+
userAccount = testData.user
40+
AppsTestFixtures.mockManifest(appManifestHttpClient, MANIFEST)
41+
42+
val json =
43+
objectMapper.readTree(
44+
performAuthPost(
45+
"/v2/organizations/${testData.organization.id}/apps",
46+
mapOf("manifestUrl" to AppsTestFixtures.MANIFEST_URL),
47+
).andIsOk.andReturn().response.contentAsString,
48+
)
49+
installId = json.get("id").asLong()
50+
performAuthPut("/v2/projects/${testData.project.id}/apps/$installId", null).andIsOk
51+
installToken = requestInstallToken(json.get("clientId").asText(), json.get("clientSecret").asText())
52+
}
53+
54+
@AfterEach
55+
fun cleanup() {
56+
testDataService.cleanTestData(testData.root)
57+
}
58+
59+
@Test
60+
fun `records the install as the actor and nobody as the author`() {
61+
createKeyAsApp("made-by-the-app")
62+
63+
asApp(get("/v2/projects/${testData.project.id}/activity")).andIsOk.andAssertThatJson {
64+
node("_embedded.activities[0].app.installId").isEqualTo(installId)
65+
node("_embedded.activities[0].app.appId").isEqualTo("test-app")
66+
node("_embedded.activities[0].app.name").isEqualTo("Test App")
67+
node("_embedded.activities[0].author").isNull()
68+
}
69+
}
70+
71+
/** Narrowed to a person, the change really is theirs — and the app is still named. */
72+
@Test
73+
fun `records both the acted-as user and the install`() {
74+
logout()
75+
perform(
76+
post("/v2/projects/${testData.project.id}/translations")
77+
.header(HttpHeaders.AUTHORIZATION, "Bearer $installToken")
78+
.header(ACT_AS_USER_HEADER, testData.user.id.toString())
79+
.contentType(MediaType.APPLICATION_JSON)
80+
.content("""{"key":"made-for-a-user","translations":{"en":"Hello"}}"""),
81+
).andIsOk
82+
83+
asApp(get("/v2/projects/${testData.project.id}/activity")).andIsOk.andAssertThatJson {
84+
node("_embedded.activities[0].app.installId").isEqualTo(installId)
85+
node("_embedded.activities[0].author.id").isEqualTo(testData.user.id)
86+
}
87+
}
88+
89+
@Test
90+
fun `leaves a person's own change attributed to them and to no app`() {
91+
userAccount = testData.user
92+
performAuthPost(
93+
"/v2/projects/${testData.project.id}/translations",
94+
mapOf("key" to "made-by-a-person", "translations" to mapOf("en" to "Hello")),
95+
).andIsOk
96+
97+
performAuthGet("/v2/projects/${testData.project.id}/activity").andIsOk.andAssertThatJson {
98+
node("_embedded.activities[0].author.id").isEqualTo(testData.user.id)
99+
node("_embedded.activities[0].app").isNull()
100+
}
101+
}
102+
103+
private fun createKeyAsApp(key: String) {
104+
asApp(
105+
post("/v2/projects/${testData.project.id}/translations")
106+
.contentType(MediaType.APPLICATION_JSON)
107+
.content("""{"key":"$key","translations":{"en":"Hello"}}"""),
108+
).andIsOk
109+
}
110+
111+
private fun asApp(builder: MockHttpServletRequestBuilder): ResultActions {
112+
logout()
113+
return perform(builder.header(HttpHeaders.AUTHORIZATION, "Bearer $installToken"))
114+
}
115+
116+
private fun requestInstallToken(
117+
clientId: String,
118+
clientSecret: String,
119+
): String {
120+
logout()
121+
val response =
122+
perform(
123+
post("/v2/public/apps/token")
124+
.contentType(MediaType.APPLICATION_JSON)
125+
.content(
126+
objectMapper.writeValueAsString(
127+
mapOf(
128+
"grant_type" to "client_credentials",
129+
"client_id" to clientId,
130+
"client_secret" to clientSecret,
131+
),
132+
),
133+
),
134+
).andIsOk.andReturn().response.contentAsString
135+
return objectMapper.readTree(response).get("access_token").asText()
136+
}
137+
138+
companion object {
139+
private const val ACT_AS_USER_HEADER = "X-Tolgee-Act-As-User-Id"
140+
141+
private val MANIFEST: String =
142+
"""
143+
{
144+
"id": "test-app",
145+
"name": "Test App",
146+
"version": "0.1.0",
147+
"baseUrl": "https://app.example.com",
148+
"scopes": ["keys.edit", "translations.edit", "activity.view"],
149+
"modules": {
150+
"project-dashboard-page": [
151+
{"key": "home", "title": "Home", "icon": "🏠", "entry": "/"}
152+
]
153+
}
154+
}
155+
""".trimIndent()
156+
}
157+
}

0 commit comments

Comments
 (0)