summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-api/src/test
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2022-04-04 17:00:31 +0200
committerGitHub <noreply@github.com>2022-04-04 17:00:31 +0200
commit38769373c7e89783d33849283586bfa0b62e8251 (patch)
tree4fda128ee6b30018c1aa14c584cc53ade80e67f7 /opendc-web/opendc-web-api/src/test
parent6021aa4278bebb34bf5603ead4b5daeabcdc4c19 (diff)
parent527ae2230f5c2dd22f496f45d5d8e3bd4acdb854 (diff)
merge: Migrate to Quarkus-based web API
This pull request changes the web API to a Quarkus-based version. Currently, the OpenDC web API is written in Python (using Flask). Although Python is a powerful language to develop web services, having another language next to Kotlin/Java and JavaScript introduces some challenges. For instance, the web API and UI lack integration with our Gradle-based build pipeline and require additional steps from the developer to start working with. Furthermore, deploying OpenDC requires having Python installed in addition to the JVM. By converting the web API into a Quarkus application, we can enjoy further integration with our Gradle-based build pipeline and simplify the development/deployment process of OpenDC, by requiring only the JVM and Node to work with OpenDC. ## Implementation Notes :hammer_and_pick: * Move build dependencies into version catalog * Design unified communication protocol * Add Quarkus API implementation * Add new web client implementation * Update runner to use new web client * Fix compatibility with React.js UI * Remove Python build steps from CI pipeline * Update Docker deployment for new web API * Remove obsolete database configuration ## External Dependencies :four_leaf_clover: * Quarkus ## Breaking API Changes :warning: * The new web API only supports SQL-based databases for storing user-data, as opposed to MongoDB currently. We intend to use H2 for development and Postgres for production.
Diffstat (limited to 'opendc-web/opendc-web-api/src/test')
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt48
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt100
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt200
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt265
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt213
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt240
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt178
-rw-r--r--opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt304
8 files changed, 1548 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt
new file mode 100644
index 00000000..e88e1c1c
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/SchedulerResourceTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest
+
+import io.quarkus.test.junit.QuarkusTest
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.junit.jupiter.api.Test
+
+/**
+ * Test suite for [SchedulerResource]
+ */
+@QuarkusTest
+class SchedulerResourceTest {
+ /**
+ * Test to verify whether we can obtain all schedulers.
+ */
+ @Test
+ fun testGetSchedulers() {
+ When {
+ get("/schedulers")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt
new file mode 100644
index 00000000..6fab9953
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/TraceResourceTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.TraceService
+import org.opendc.web.proto.Trace
+
+/**
+ * Test suite for [TraceResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(TraceResource::class)
+class TraceResourceTest {
+ @InjectMock
+ private lateinit var traceService: TraceService
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(traceService, TraceService::class.java)
+ }
+
+ /**
+ * Test that tries to obtain all traces (empty response).
+ */
+ @Test
+ fun testGetAllEmpy() {
+ every { traceService.findAll() } returns emptyList()
+
+ When {
+ get()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("", Matchers.empty<String>())
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent trace.
+ */
+ @Test
+ fun testGetNonExisting() {
+ every { traceService.findById("bitbrains") } returns null
+
+ When {
+ get("/bitbrains")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain an existing trace.
+ */
+ @Test
+ fun testGetExisting() {
+ every { traceService.findById("bitbrains") } returns Trace("bitbrains", "Bitbrains", "VM")
+
+ When {
+ get("/bitbrains")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("name", equalTo("Bitbrains"))
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt
new file mode 100644
index 00000000..b82c60e8
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/runner/JobResourceTest.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest.runner
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.security.TestSecurity
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Given
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.JobService
+import org.opendc.web.proto.*
+import org.opendc.web.proto.Targets
+import org.opendc.web.proto.runner.Job
+import org.opendc.web.proto.runner.Portfolio
+import org.opendc.web.proto.runner.Scenario
+import org.opendc.web.proto.runner.Topology
+import java.time.Instant
+
+/**
+ * Test suite for [JobResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(JobResource::class)
+class JobResourceTest {
+ @InjectMock
+ private lateinit var jobService: JobService
+
+ /**
+ * Dummy values
+ */
+ private val dummyPortfolio = Portfolio(1, 1, "test", Targets(emptySet()))
+ private val dummyTopology = Topology(1, 1, "test", emptyList(), Instant.now(), Instant.now())
+ private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm")
+ private val dummyScenario = Scenario(1, 1, dummyPortfolio, "test", Workload(dummyTrace, 1.0), dummyTopology, OperationalPhenomena(false, false), "test",)
+ private val dummyJob = Job(1, dummyScenario, JobState.PENDING, Instant.now(), Instant.now())
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(jobService, JobService::class.java)
+ }
+
+ /**
+ * Test that tries to query the pending jobs without token.
+ */
+ @Test
+ fun testQueryWithoutToken() {
+ When {
+ get()
+ } Then {
+ statusCode(401)
+ }
+ }
+
+ /**
+ * Test that tries to query the pending jobs for a user.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testQueryInvalidScope() {
+ When {
+ get()
+ } Then {
+ statusCode(403)
+ }
+ }
+
+ /**
+ * Test that tries to query the pending jobs for a runner.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testQuery() {
+ every { jobService.queryPending() } returns listOf(dummyJob)
+
+ When {
+ get()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("get(0).id", equalTo(1))
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent job.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetNonExisting() {
+ every { jobService.findById(1) } returns null
+
+ When {
+ get("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a job.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetExisting() {
+ every { jobService.findById(1) } returns dummyJob
+
+ When {
+ get("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", equalTo(1))
+ }
+ }
+
+ /**
+ * Test that tries to update a non-existent job.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testUpdateNonExistent() {
+ every { jobService.updateState(1, any(), any()) } returns null
+
+ Given {
+ body(Job.Update(JobState.PENDING))
+ contentType(ContentType.JSON)
+ } When {
+ post("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to update a job.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testUpdateState() {
+ every { jobService.updateState(1, any(), any()) } returns dummyJob.copy(state = JobState.CLAIMED)
+
+ Given {
+ body(Job.Update(JobState.CLAIMED))
+ contentType(ContentType.JSON)
+ } When {
+ post("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("state", equalTo(JobState.CLAIMED.toString()))
+ }
+ }
+
+ /**
+ * Test that tries to update a job with invalid input.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testUpdateInvalidInput() {
+ Given {
+ body("""{ "test": "test" }""")
+ contentType(ContentType.JSON)
+ } When {
+ post("/1")
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt
new file mode 100644
index 00000000..f74efbca
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioResourceTest.kt
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest.user
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.security.TestSecurity
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Given
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.PortfolioService
+import org.opendc.web.proto.Targets
+import org.opendc.web.proto.user.Portfolio
+import org.opendc.web.proto.user.Project
+import org.opendc.web.proto.user.ProjectRole
+import java.time.Instant
+
+/**
+ * Test suite for [PortfolioResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(PortfolioResource::class)
+class PortfolioResourceTest {
+ @InjectMock
+ private lateinit var portfolioService: PortfolioService
+
+ /**
+ * Dummy project and portfolio
+ */
+ private val dummyProject = Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER)
+ private val dummyPortfolio = Portfolio(1, 1, dummyProject, "test", Targets(emptySet(), 1), emptyList())
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(portfolioService, PortfolioService::class.java)
+ }
+
+ /**
+ * Test that tries to obtain the list of portfolios belonging to a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetForProject() {
+ every { portfolioService.findAll("testUser", 1) } returns emptyList()
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to create a topology for a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateNonExistent() {
+ every { portfolioService.create("testUser", 1, any()) } returns null
+
+ Given {
+ pathParam("project", "1")
+
+ body(Portfolio.Create("test", Targets(emptySet(), 1)))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to create a portfolio for a scenario.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreate() {
+ every { portfolioService.create("testUser", 1, any()) } returns dummyPortfolio
+
+ Given {
+ pathParam("project", "1")
+
+ body(Portfolio.Create("test", Targets(emptySet(), 1)))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", Matchers.equalTo(1))
+ body("name", Matchers.equalTo("test"))
+ }
+ }
+
+ /**
+ * Test to create a portfolio with an empty body.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateEmpty() {
+ Given {
+ pathParam("project", "1")
+
+ body("{}")
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to create a portfolio with a blank name.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateBlankName() {
+ Given {
+ pathParam("project", "1")
+
+ body(Portfolio.Create("", Targets(emptySet(), 1)))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a portfolio without token.
+ */
+ @Test
+ fun testGetWithoutToken() {
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(401)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a portfolio with an invalid scope.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetInvalidToken() {
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(403)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetNonExisting() {
+ every { portfolioService.findOne("testUser", 1, 1) } returns null
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetExisting() {
+ every { portfolioService.findOne("testUser", 1, 1) } returns dummyPortfolio
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", Matchers.equalTo(1))
+ }
+ }
+
+ /**
+ * Test to delete a non-existent portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDeleteNonExistent() {
+ every { portfolioService.delete("testUser", 1, 1) } returns null
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ delete("/1")
+ } Then {
+ statusCode(404)
+ }
+ }
+
+ /**
+ * Test to delete a portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDelete() {
+ every { portfolioService.delete("testUser", 1, 1) } returns dummyPortfolio
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ delete("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt
new file mode 100644
index 00000000..dbafa8c0
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/PortfolioScenarioResourceTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest.user
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.security.TestSecurity
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Given
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.ScenarioService
+import org.opendc.web.proto.*
+import org.opendc.web.proto.user.*
+import java.time.Instant
+
+/**
+ * Test suite for [PortfolioScenarioResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(PortfolioScenarioResource::class)
+class PortfolioScenarioResourceTest {
+ @InjectMock
+ private lateinit var scenarioService: ScenarioService
+
+ /**
+ * Dummy values
+ */
+ private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER)
+ private val dummyPortfolio = Portfolio.Summary(1, 1, "test", Targets(emptySet()))
+ private val dummyJob = Job(1, JobState.PENDING, Instant.now(), Instant.now(), null)
+ private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm")
+ private val dummyTopology = Topology.Summary(1, 1, "test", Instant.now(), Instant.now())
+ private val dummyScenario = Scenario(
+ 1,
+ 1,
+ dummyProject,
+ dummyPortfolio,
+ "test",
+ Workload(dummyTrace, 1.0),
+ dummyTopology,
+ OperationalPhenomena(false, false),
+ "test",
+ dummyJob
+ )
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(scenarioService, ScenarioService::class.java)
+ }
+
+ /**
+ * Test that tries to obtain a portfolio without token.
+ */
+ @Test
+ fun testGetWithoutToken() {
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+ } When {
+ get()
+ } Then {
+ statusCode(401)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a portfolio with an invalid scope.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetInvalidToken() {
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+ } When {
+ get()
+ } Then {
+ statusCode(403)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGet() {
+ every { scenarioService.findAll("testUser", 1, 1) } returns emptyList()
+
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+ } When {
+ get()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to create a scenario for a portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateNonExistent() {
+ every { scenarioService.create("testUser", 1, any(), any()) } returns null
+
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+
+ body(Scenario.Create("test", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test"))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to create a scenario for a portfolio.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreate() {
+ every { scenarioService.create("testUser", 1, 1, any()) } returns dummyScenario
+
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+
+ body(Scenario.Create("test", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test"))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", Matchers.equalTo(1))
+ body("name", Matchers.equalTo("test"))
+ }
+ }
+
+ /**
+ * Test to create a project with an empty body.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateEmpty() {
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+
+ body("{}")
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to create a project with a blank name.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateBlankName() {
+ Given {
+ pathParam("project", "1")
+ pathParam("portfolio", "1")
+
+ body(Scenario.Create("", Workload.Spec("test", 1.0), 1, OperationalPhenomena(false, false), "test"))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt
new file mode 100644
index 00000000..bcbcbab1
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ProjectResourceTest.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest.user
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.security.TestSecurity
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Given
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.ProjectService
+import org.opendc.web.proto.user.Project
+import org.opendc.web.proto.user.ProjectRole
+import java.time.Instant
+
+/**
+ * Test suite for [ProjectResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(ProjectResource::class)
+class ProjectResourceTest {
+ @InjectMock
+ private lateinit var projectService: ProjectService
+
+ /**
+ * Dummy values.
+ */
+ private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER)
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(projectService, ProjectService::class.java)
+ }
+
+ /**
+ * Test that tries to obtain all projects without token.
+ */
+ @Test
+ fun testGetAllWithoutToken() {
+ When {
+ get()
+ } Then {
+ statusCode(401)
+ }
+ }
+
+ /**
+ * Test that tries to obtain all projects with an invalid scope.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetAllWithInvalidScope() {
+ When {
+ get()
+ } Then {
+ statusCode(403)
+ }
+ }
+
+ /**
+ * Test that tries to obtain all project for a user.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetAll() {
+ val projects = listOf(dummyProject)
+ every { projectService.findWithUser("testUser") } returns projects
+
+ When {
+ get()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("get(0).name", equalTo("test"))
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetNonExisting() {
+ every { projectService.findWithUser("testUser", 1) } returns null
+
+ When {
+ get("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a job.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetExisting() {
+ every { projectService.findWithUser("testUser", 1) } returns dummyProject
+
+ When {
+ get("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", equalTo(0))
+ }
+ }
+
+ /**
+ * Test that tries to create a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreate() {
+ every { projectService.createForUser("testUser", "test") } returns dummyProject
+
+ Given {
+ body(Project.Create("test"))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", equalTo(0))
+ body("name", equalTo("test"))
+ }
+ }
+
+ /**
+ * Test to create a project with an empty body.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateEmpty() {
+ Given {
+ body("{}")
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to create a project with a blank name.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateBlankName() {
+ Given {
+ body(Project.Create(""))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to delete a non-existent project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDeleteNonExistent() {
+ every { projectService.deleteWithUser("testUser", 1) } returns null
+
+ When {
+ delete("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to delete a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDelete() {
+ every { projectService.deleteWithUser("testUser", 1) } returns dummyProject
+
+ When {
+ delete("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to delete a project which the user does not own.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDeleteNonOwner() {
+ every { projectService.deleteWithUser("testUser", 1) } throws IllegalArgumentException("User does not own project")
+
+ When {
+ delete("/1")
+ } Then {
+ statusCode(403)
+ contentType(ContentType.JSON)
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt
new file mode 100644
index 00000000..65e6e9a1
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/ScenarioResourceTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest.user
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.security.TestSecurity
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Given
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.ScenarioService
+import org.opendc.web.proto.*
+import org.opendc.web.proto.user.*
+import java.time.Instant
+
+/**
+ * Test suite for [ScenarioResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(ScenarioResource::class)
+class ScenarioResourceTest {
+ @InjectMock
+ private lateinit var scenarioService: ScenarioService
+
+ /**
+ * Dummy values
+ */
+ private val dummyProject = Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER)
+ private val dummyPortfolio = Portfolio.Summary(1, 1, "test", Targets(emptySet()))
+ private val dummyJob = Job(1, JobState.PENDING, Instant.now(), Instant.now(), null)
+ private val dummyTrace = Trace("bitbrains", "Bitbrains", "vm")
+ private val dummyTopology = Topology.Summary(1, 1, "test", Instant.now(), Instant.now())
+ private val dummyScenario = Scenario(
+ 1,
+ 1,
+ dummyProject,
+ dummyPortfolio,
+ "test",
+ Workload(dummyTrace, 1.0),
+ dummyTopology,
+ OperationalPhenomena(false, false),
+ "test",
+ dummyJob
+ )
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(scenarioService, ScenarioService::class.java)
+ }
+
+ /**
+ * Test that tries to obtain a scenario without token.
+ */
+ @Test
+ fun testGetWithoutToken() {
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(401)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a scenario with an invalid scope.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetInvalidToken() {
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(403)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent scenario.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetNonExisting() {
+ every { scenarioService.findOne("testUser", 1, 1) } returns null
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a scenario.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetExisting() {
+ every { scenarioService.findOne("testUser", 1, 1) } returns dummyScenario
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", Matchers.equalTo(1))
+ }
+ }
+
+ /**
+ * Test to delete a non-existent scenario.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDeleteNonExistent() {
+ every { scenarioService.delete("testUser", 1, 1) } returns null
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ delete("/1")
+ } Then {
+ statusCode(404)
+ }
+ }
+
+ /**
+ * Test to delete a scenario.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDelete() {
+ every { scenarioService.delete("testUser", 1, 1) } returns dummyScenario
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ delete("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+}
diff --git a/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt
new file mode 100644
index 00000000..ececeaca
--- /dev/null
+++ b/opendc-web/opendc-web-api/src/test/kotlin/org/opendc/web/api/rest/user/TopologyResourceTest.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2022 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.opendc.web.api.rest.user
+
+import io.mockk.every
+import io.quarkiverse.test.junit.mockk.InjectMock
+import io.quarkus.test.common.http.TestHTTPEndpoint
+import io.quarkus.test.junit.QuarkusMock
+import io.quarkus.test.junit.QuarkusTest
+import io.quarkus.test.security.TestSecurity
+import io.restassured.http.ContentType
+import io.restassured.module.kotlin.extensions.Given
+import io.restassured.module.kotlin.extensions.Then
+import io.restassured.module.kotlin.extensions.When
+import org.hamcrest.Matchers
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.opendc.web.api.service.TopologyService
+import org.opendc.web.proto.user.Project
+import org.opendc.web.proto.user.ProjectRole
+import org.opendc.web.proto.user.Topology
+import java.time.Instant
+
+/**
+ * Test suite for [TopologyResource].
+ */
+@QuarkusTest
+@TestHTTPEndpoint(TopologyResource::class)
+class TopologyResourceTest {
+ @InjectMock
+ private lateinit var topologyService: TopologyService
+
+ /**
+ * Dummy project and topology.
+ */
+ private val dummyProject = Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER)
+ private val dummyTopology = Topology(1, 1, dummyProject, "test", emptyList(), Instant.now(), Instant.now())
+
+ @BeforeEach
+ fun setUp() {
+ QuarkusMock.installMockForType(topologyService, TopologyService::class.java)
+ }
+
+ /**
+ * Test that tries to obtain the list of topologies belonging to a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetForProject() {
+ every { topologyService.findAll("testUser", 1) } returns emptyList()
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to create a topology for a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateNonExistent() {
+ every { topologyService.create("testUser", 1, any()) } returns null
+
+ Given {
+ pathParam("project", "1")
+
+ body(Topology.Create("test", emptyList()))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to create a topology for a project.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreate() {
+ every { topologyService.create("testUser", 1, any()) } returns dummyTopology
+
+ Given {
+ pathParam("project", "1")
+
+ body(Topology.Create("test", emptyList()))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", Matchers.equalTo(1))
+ body("name", Matchers.equalTo("test"))
+ }
+ }
+
+ /**
+ * Test to create a topology with an empty body.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateEmpty() {
+ Given {
+ pathParam("project", "1")
+
+ body("{}")
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to create a topology with a blank name.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testCreateBlankName() {
+ Given {
+ pathParam("project", "1")
+
+ body(Topology.Create("", emptyList()))
+ contentType(ContentType.JSON)
+ } When {
+ post()
+ } Then {
+ statusCode(400)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a topology without token.
+ */
+ @Test
+ fun testGetWithoutToken() {
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(401)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a topology with an invalid scope.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["runner"])
+ fun testGetInvalidToken() {
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(403)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a non-existent topology.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetNonExisting() {
+ every { topologyService.findOne("testUser", 1, 1) } returns null
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(404)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test that tries to obtain a topology.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testGetExisting() {
+ every { topologyService.findOne("testUser", 1, 1) } returns dummyTopology
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ get("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ body("id", Matchers.equalTo(1))
+ println(extract().asPrettyString())
+ }
+ }
+
+ /**
+ * Test to delete a non-existent topology.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testUpdateNonExistent() {
+ every { topologyService.update("testUser", any(), any(), any()) } returns null
+
+ Given {
+ pathParam("project", "1")
+ body(Topology.Update(emptyList()))
+ contentType(ContentType.JSON)
+ } When {
+ put("/1")
+ } Then {
+ statusCode(404)
+ }
+ }
+
+ /**
+ * Test to update a topology.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testUpdate() {
+ every { topologyService.update("testUser", any(), any(), any()) } returns dummyTopology
+
+ Given {
+ pathParam("project", "1")
+ body(Topology.Update(emptyList()))
+ contentType(ContentType.JSON)
+ } When {
+ put("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+
+ /**
+ * Test to delete a non-existent topology.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDeleteNonExistent() {
+ every { topologyService.delete("testUser", 1, 1) } returns null
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ delete("/1")
+ } Then {
+ statusCode(404)
+ }
+ }
+
+ /**
+ * Test to delete a topology.
+ */
+ @Test
+ @TestSecurity(user = "testUser", roles = ["openid"])
+ fun testDelete() {
+ every { topologyService.delete("testUser", 1, 1) } returns dummyTopology
+
+ Given {
+ pathParam("project", "1")
+ } When {
+ delete("/1")
+ } Then {
+ statusCode(200)
+ contentType(ContentType.JSON)
+ }
+ }
+}