diff options
43 files changed, 2238 insertions, 2260 deletions
diff --git a/buildSrc/src/main/kotlin/quarkus-conventions.gradle.kts b/buildSrc/src/main/kotlin/quarkus-conventions.gradle.kts index f1adb182..3bd3d85a 100644 --- a/buildSrc/src/main/kotlin/quarkus-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/quarkus-conventions.gradle.kts @@ -29,25 +29,11 @@ import org.gradle.kotlin.dsl.kotlin import org.gradle.kotlin.dsl.withType plugins { - id("kotlin-conventions") - kotlin("plugin.allopen") - kotlin("plugin.jpa") + id("java-conventions") id("testing-conventions") id("io.quarkus") } -/* Mark necessary classes as open in Kotlin */ -allOpen { - annotation("javax.ws.rs.Path") - annotation("javax.enterprise.context.ApplicationScoped") - annotation("io.quarkus.test.junit.QuarkusTest") - annotation("javax.persistence.Entity") -} - -/* Include metadata for method parameters */ -tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { - kotlinOptions.javaParameters = true -} /* Launch Quarkus dev mode from project root directory */ tasks.quarkusDev { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 186c1388..2581f1ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,8 +23,7 @@ mockk = "1.13.2" node = "18.12.0" parquet = "1.12.3" progressbar = "0.9.3" -quarkus = "2.14.2.Final" -quarkus-junit5-mockk = "1.1.1" +quarkus = "2.16.1.Final" sentry = "6.8.0" slf4j = "2.0.4" spotless = "6.12.0" @@ -89,12 +88,10 @@ hypersistence-utils-hibernate = { module = "io.hypersistence:hypersistence-utils # Quarkus (Testing) quarkus-junit5-core = { module = "io.quarkus:quarkus-junit5" } -quarkus-junit5-mockk = { module = "io.quarkiverse.mockk:quarkus-junit5-mockk", version.ref = "quarkus-junit5-mockk" } quarkus-jacoco = { module = "io.quarkus:quarkus-jacoco" } quarkus-panache-mock = { module = "io.quarkus:quarkus-panache-mock" } quarkus-test-security = { module = "io.quarkus:quarkus-test-security" } restassured-core = { module = "io.rest-assured:rest-assured" } -restassured-kotlin = { module = "io.rest-assured:kotlin-extensions" } # Calcite (SQL) calcite-core = { module = "org.apache.calcite:calcite-core", version.ref = "calcite" } diff --git a/opendc-web/opendc-web-server/build.gradle.kts b/opendc-web/opendc-web-server/build.gradle.kts index bbf713cd..d05210ac 100644 --- a/opendc-web/opendc-web-server/build.gradle.kts +++ b/opendc-web/opendc-web-server/build.gradle.kts @@ -55,11 +55,9 @@ dependencies { implementation(libs.hypersistence.utils.hibernate) testImplementation(libs.quarkus.junit5.core) - testImplementation(libs.quarkus.junit5.mockk) testImplementation(libs.quarkus.jacoco) testImplementation(libs.quarkus.panache.mock) testImplementation(libs.restassured.core) - testImplementation(libs.restassured.kotlin) testImplementation(libs.quarkus.test.security) testImplementation(libs.quarkus.jdbc.h2) } diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/SchedulerResource.kt b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java index 919b25fc..0fd58182 100644 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/SchedulerResource.kt +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2023 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 @@ -20,29 +20,33 @@ * SOFTWARE. */ -package org.opendc.web.server.rest +package org.opendc.web.server.rest; -import javax.ws.rs.GET -import javax.ws.rs.Path +import java.util.List; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; /** * A resource representing the available schedulers that can be used during experiments. */ +@Produces("application/json") @Path("/schedulers") -class SchedulerResource { +public final class SchedulerResource { /** * Obtain all available schedulers. */ @GET - fun getAll() = listOf( - "mem", - "mem-inv", - "core-mem", - "core-mem-inv", - "active-servers", - "active-servers-inv", - "provisioned-cores", - "provisioned-cores-inv", - "random" - ) + public List<String> getAll() { + return List.of( + "mem", + "mem-inv", + "core-mem", + "core-mem-inv", + "active-servers", + "active-servers-inv", + "provisioned-cores", + "provisioned-cores-inv", + "random"); + } } diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java index 94b8340b..2b1efb02 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/TraceService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java @@ -20,30 +20,45 @@ * SOFTWARE. */ -package org.opendc.web.server.service; +package org.opendc.web.server.rest; import java.util.List; -import javax.enterprise.context.ApplicationScoped; +import java.util.stream.Stream; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import org.opendc.web.server.model.Trace; /** - * Service for managing {@link Trace}s. + * A resource representing the workload traces available in the OpenDC instance. */ -@ApplicationScoped -public final class TraceService { +@Produces("application/json") +@Path("/traces") +public final class TraceResource { /** - * Obtain all available workload traces. + * Obtain all available traces. */ - public List<org.opendc.web.proto.Trace> findAll() { - List<Trace> entities = Trace.listAll(); - return entities.stream().map(TraceService::toUserDto).toList(); + @GET + public List<org.opendc.web.proto.Trace> getAll() { + Stream<Trace> entities = Trace.streamAll(); + return entities.map(TraceResource::toUserDto).toList(); } /** - * Obtain a workload trace by identifier. + * Obtain trace information by identifier. */ - public org.opendc.web.proto.Trace findById(String id) { - return toUserDto(Trace.findById(id)); + @GET + @Path("{id}") + public org.opendc.web.proto.Trace get(@PathParam("id") String id) { + Trace trace = Trace.findById(id); + + if (trace == null) { + throw new WebApplicationException("Trace not found", 404); + } + + return toUserDto(trace); } /** diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.kt b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java index e50917aa..3b6be42e 100644 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.kt +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 AtLarge Research + * Copyright (c) 2023 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 @@ -20,24 +20,27 @@ * SOFTWARE. */ -package org.opendc.web.server.rest.error +package org.opendc.web.server.rest.error; -import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException -import org.opendc.web.proto.ProtocolError -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response -import javax.ws.rs.ext.ExceptionMapper -import javax.ws.rs.ext.Provider +import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.opendc.web.proto.ProtocolError; /** * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson. */ @Provider -class MissingKotlinParameterExceptionMapper : ExceptionMapper<MissingKotlinParameterException> { - override fun toResponse(exception: MissingKotlinParameterException): Response { +public final class MissingKotlinParameterExceptionMapper implements ExceptionMapper<MissingKotlinParameterException> { + @Override + public Response toResponse(MissingKotlinParameterException exception) { return Response.status(Response.Status.BAD_REQUEST) - .entity(ProtocolError(Response.Status.BAD_REQUEST.statusCode, "Field '${exception.parameter.name}' is missing from body.")) - .type(MediaType.APPLICATION_JSON) - .build() + .entity(new ProtocolError( + Response.Status.BAD_REQUEST.getStatusCode(), + "Field " + exception.getParameter().getName() + " is missing from body.")) + .type(MediaType.APPLICATION_JSON) + .build(); } } diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.kt b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java index aa046abf..ad1bb05e 100644 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.kt +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 AtLarge Research + * Copyright (c) 2023 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 @@ -20,26 +20,32 @@ * SOFTWARE. */ -package org.opendc.web.server.rest.error +package org.opendc.web.server.rest.error; -import org.opendc.web.proto.ProtocolError -import javax.ws.rs.WebApplicationException -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response -import javax.ws.rs.ext.ExceptionMapper -import javax.ws.rs.ext.Provider +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.opendc.web.proto.ProtocolError; /** - * Helper class to transform a [WebApplicationException] into an JSON error response. + * Helper class to transform a {@link WebApplicationException} into an JSON error response. */ @Provider -class WebApplicationExceptionMapper : ExceptionMapper<WebApplicationException> { - override fun toResponse(exception: WebApplicationException): Response { - val code = exception.response.status +public final class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> { + @Override + public Response toResponse(WebApplicationException exception) { + int code = exception.getResponse().getStatus(); + + String message = exception.getMessage(); + if (message == null) { + message = "Unknown error"; + } return Response.status(code) - .entity(ProtocolError(code, exception.message ?: "Unknown error")) - .type(MediaType.APPLICATION_JSON) - .build() + .entity(new ProtocolError(code, message)) + .type(MediaType.APPLICATION_JSON) + .build(); } } diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java new file mode 100644 index 00000000..134c6814 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023 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.server.rest.runner; + +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.JobService; + +/** + * A resource representing the available simulation jobs. + */ +@Produces("application/json") +@Path("/jobs") +@RolesAllowed("runner") +public final class JobResource { + /** + * The {@link JobService} responsible for managing the jobs. + */ + private final JobService jobService; + + /** + * Construct a {@link JobResource} instance. + * + * @param jobService The {@link JobService} responsible for managing the jobs. + */ + public JobResource(JobService jobService) { + this.jobService = jobService; + } + + /** + * Obtain all pending simulation jobs. + */ + @GET + public List<org.opendc.web.proto.runner.Job> queryPending() { + return jobService.listPending(); + } + + /** + * Get a job by identifier. + */ + @GET + @Path("{job}") + public org.opendc.web.proto.runner.Job get(@PathParam("job") long id) { + org.opendc.web.proto.runner.Job job = jobService.findById(id); + if (job == null) { + throw new WebApplicationException("Job not found", 404); + } + + return job; + } + + /** + * Atomically update the state of a job. + */ + @POST + @Path("{job}") + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.runner.Job update( + @PathParam("job") long id, @Valid org.opendc.web.proto.runner.Job.Update update) { + try { + var job = jobService.updateState(id, update.getState(), update.getRuntime(), update.getResults()); + if (job == null) { + throw new WebApplicationException("Job not found", 404); + } + + return job; + } catch (IllegalArgumentException e) { + throw new WebApplicationException(e, 400); + } catch (IllegalStateException e) { + throw new WebApplicationException(e, 409); + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java new file mode 100644 index 00000000..e8e05f97 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.PortfolioService; + +/** + * A resource representing the portfolios of a project. + */ +@Produces("application/json") +@Path("/projects/{project}/portfolios") +@RolesAllowed("openid") +public final class PortfolioResource { + /** + * The service for managing the user portfolios. + */ + private final PortfolioService portfolioService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link PortfolioResource}. + * + * @param portfolioService The {@link PortfolioService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public PortfolioResource(PortfolioService portfolioService, SecurityIdentity identity) { + this.portfolioService = portfolioService; + this.identity = identity; + } + + /** + * Get all portfolios that belong to the specified project. + */ + @GET + public List<org.opendc.web.proto.user.Portfolio> getAll(@PathParam("project") long projectId) { + return portfolioService.findByUser(identity.getPrincipal().getName(), projectId); + } + + /** + * Create a portfolio for this project. + */ + @POST + @Transactional + public org.opendc.web.proto.user.Portfolio create( + @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Portfolio.Create request) { + var portfolio = portfolioService.create(identity.getPrincipal().getName(), projectId, request); + if (portfolio == null) { + throw new WebApplicationException("Project not found", 404); + } + + return portfolio; + } + + /** + * Obtain a portfolio by its identifier. + */ + @GET + @Path("{portfolio}") + public org.opendc.web.proto.user.Portfolio get( + @PathParam("project") long projectId, @PathParam("portfolio") int number) { + var portfolio = portfolioService.findByUser(identity.getPrincipal().getName(), projectId, number); + if (portfolio == null) { + throw new WebApplicationException("Portfolio not found", 404); + } + + return portfolio; + } + + /** + * Delete a portfolio. + */ + @DELETE + @Path("{portfolio}") + @Transactional + public org.opendc.web.proto.user.Portfolio delete( + @PathParam("project") long projectId, @PathParam("portfolio") int number) { + var portfolio = portfolioService.delete(identity.getPrincipal().getName(), projectId, number); + if (portfolio == null) { + throw new WebApplicationException("Portfolio not found", 404); + } + + return portfolio; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java new file mode 100644 index 00000000..a6db7c54 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.proto.user.Scenario; +import org.opendc.web.server.service.ScenarioService; + +/** + * A resource representing the scenarios of a portfolio. + */ +@Path("/projects/{project}/portfolios/{portfolio}/scenarios") +@RolesAllowed("openid") +public final class PortfolioScenarioResource { + /** + * The service for managing the user scenarios. + */ + private final ScenarioService scenarioService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link PortfolioScenarioResource}. + * + * @param scenarioService The {@link ScenarioService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public PortfolioScenarioResource(ScenarioService scenarioService, SecurityIdentity identity) { + this.scenarioService = scenarioService; + this.identity = identity; + } + + /** + * Get all scenarios that belong to the specified portfolio. + */ + @GET + public List<Scenario> get(@PathParam("project") long projectId, @PathParam("portfolio") int portfolioNumber) { + return scenarioService.findAll(identity.getPrincipal().getName(), projectId, portfolioNumber); + } + + /** + * Create a scenario for this portfolio. + */ + @POST + @Transactional + public org.opendc.web.proto.user.Scenario create( + @PathParam("project") long projectId, + @PathParam("portfolio") int portfolioNumber, + @Valid org.opendc.web.proto.user.Scenario.Create request) { + var scenario = scenarioService.create(identity.getPrincipal().getName(), projectId, portfolioNumber, request); + if (scenario == null) { + throw new WebApplicationException("Portfolio not found", 404); + } + + return scenario; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java new file mode 100644 index 00000000..b0b8eb4e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.ProjectService; + +/** + * A resource representing the created projects. + */ +@Produces("application/json") +@Path("/projects") +@RolesAllowed("openid") +public final class ProjectResource { + /** + * The service for managing the user projects. + */ + private final ProjectService projectService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link ProjectResource}. + * + * @param projectService The {@link ProjectService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public ProjectResource(ProjectService projectService, SecurityIdentity identity) { + this.projectService = projectService; + this.identity = identity; + } + + /** + * Obtain all the projects of the current user. + */ + @GET + public List<org.opendc.web.proto.user.Project> getAll() { + return projectService.findByUser(identity.getPrincipal().getName()); + } + + /** + * Create a new project for the current user. + */ + @POST + @Transactional + @Consumes("application/json") + public org.opendc.web.proto.user.Project create(@Valid org.opendc.web.proto.user.Project.Create request) { + return projectService.create(identity.getPrincipal().getName(), request.getName()); + } + + /** + * Obtain a single project by its identifier. + */ + @GET + @Path("{project}") + public org.opendc.web.proto.user.Project get(@PathParam("project") long id) { + var project = projectService.findByUser(identity.getPrincipal().getName(), id); + if (project == null) { + throw new WebApplicationException("Project not found", 404); + } + + return project; + } + + /** + * Delete a project. + */ + @DELETE + @Path("{project}") + @Transactional + public org.opendc.web.proto.user.Project delete(@PathParam("project") long id) { + try { + var project = projectService.delete(identity.getPrincipal().getName(), id); + if (project == null) { + throw new WebApplicationException("Project not found", 404); + } + + return project; + } catch (IllegalArgumentException e) { + throw new WebApplicationException(e.getMessage(), 403); + } + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java new file mode 100644 index 00000000..a6838148 --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.ScenarioService; + +/** + * A resource representing the scenarios of a portfolio. + */ +@Produces("application/json") +@Path("/projects/{project}/scenarios") +@RolesAllowed("openid") +public final class ScenarioResource { + /** + * The service for managing the user scenarios. + */ + private final ScenarioService scenarioService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link ScenarioResource}. + * + * @param scenarioService The {@link ScenarioService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public ScenarioResource(ScenarioService scenarioService, SecurityIdentity identity) { + this.scenarioService = scenarioService; + this.identity = identity; + } + + /** + * Obtain a scenario by its identifier. + */ + @GET + @Path("{scenario}") + public org.opendc.web.proto.user.Scenario get( + @PathParam("project") long projectId, @PathParam("scenario") int number) { + var scenario = scenarioService.findOne(identity.getPrincipal().getName(), projectId, number); + if (scenario == null) { + throw new WebApplicationException("Scenario not found", 404); + } + + return scenario; + } + + /** + * Delete a scenario. + */ + @DELETE + @Path("{scenario}") + @Transactional + public org.opendc.web.proto.user.Scenario delete( + @PathParam("project") long projectId, @PathParam("scenario") int number) { + var scenario = scenarioService.delete(identity.getPrincipal().getName(), projectId, number); + if (scenario == null) { + throw new WebApplicationException("Scenario not found", 404); + } + + return scenario; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java new file mode 100644 index 00000000..54afc1ce --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import java.util.List; +import javax.annotation.security.RolesAllowed; +import javax.transaction.Transactional; +import javax.validation.Valid; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import org.opendc.web.server.service.TopologyService; + +/** + * A resource representing the constructed datacenter topologies. + */ +@Produces("application/json") +@Path("/projects/{project}/topologies") +@RolesAllowed("openid") +public final class TopologyResource { + /** + * The service for managing the user topologies. + */ + private final TopologyService topologyService; + + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link TopologyResource}. + * + * @param topologyService The {@link TopologyService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. + */ + public TopologyResource(TopologyService topologyService, SecurityIdentity identity) { + this.topologyService = topologyService; + this.identity = identity; + } + + /** + * Get all topologies that belong to the specified project. + */ + @GET + public List<org.opendc.web.proto.user.Topology> getAll(@PathParam("project") long projectId) { + return topologyService.findAll(identity.getPrincipal().getName(), projectId); + } + + /** + * Create a topology for this project. + */ + @POST + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.user.Topology create( + @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Topology.Create request) { + var topology = topologyService.create(identity.getPrincipal().getName(), projectId, request); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } + + /** + * Obtain a topology by its number. + */ + @GET + @Path("{topology}") + public org.opendc.web.proto.user.Topology get( + @PathParam("project") long projectId, @PathParam("topology") int number) { + var topology = topologyService.findOne(identity.getPrincipal().getName(), projectId, number); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } + + /** + * Update the specified topology by its number. + */ + @PUT + @Path("{topology}") + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.user.Topology update( + @PathParam("project") long projectId, + @PathParam("topology") int number, + @Valid org.opendc.web.proto.user.Topology.Update request) { + var topology = topologyService.update(identity.getPrincipal().getName(), projectId, number, request); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } + + /** + * Delete the specified topology. + */ + @Path("{topology}") + @DELETE + @Transactional + public org.opendc.web.proto.user.Topology delete( + @PathParam("project") long projectId, @PathParam("topology") int number) { + var topology = topologyService.delete(identity.getPrincipal().getName(), projectId, number); + + if (topology == null) { + throw new WebApplicationException("Topology not found", 404); + } + + return topology; + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java index b46b799b..c3fb2866 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java @@ -20,36 +20,51 @@ * SOFTWARE. */ -package org.opendc.web.server.service; +package org.opendc.web.server.rest.user; import io.quarkus.security.identity.SecurityIdentity; -import javax.enterprise.context.ApplicationScoped; +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; import org.opendc.web.proto.user.User; import org.opendc.web.proto.user.UserAccounting; +import org.opendc.web.server.service.UserAccountingService; /** - * Service for managing {@link User}s. + * A resource representing the active user. */ -@ApplicationScoped -public final class UserService { +@Produces("application/json") +@Path("/users") +@RolesAllowed("openid") +public final class UserResource { /** * The service for managing the user accounting. */ private final UserAccountingService accountingService; /** - * Construct a {@link UserService} instance. + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link UserResource}. * * @param accountingService The {@link UserAccountingService} instance to use. + * @param identity The {@link SecurityIdentity} of the current user. */ - public UserService(UserAccountingService accountingService) { + public UserResource(UserAccountingService accountingService, SecurityIdentity identity) { this.accountingService = accountingService; + this.identity = identity; } /** - * Obtain the {@link User} object for the specified <code>identity</code>. + * Get the current active user data. */ - public User getUser(SecurityIdentity identity) { + @GET + @Path("me") + public User get() { String userId = identity.getPrincipal().getName(); UserAccounting accounting = accountingService.getAccounting(userId); diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java index eb0982ec..47f44d27 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java @@ -61,7 +61,13 @@ public final class JobService { * Find a job by its identifier. */ public org.opendc.web.proto.runner.Job findById(long id) { - return toRunnerDto(Job.findById(id)); + Job job = Job.findById(id); + + if (job == null) { + return null; + } + + return toRunnerDto(job); } /** diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java index bf5206af..6a70db1e 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/ScenarioService.java @@ -219,6 +219,13 @@ public final class ScenarioService { * Convert a {@link Workload} entity into a DTO. */ public static org.opendc.web.proto.Workload toDto(Workload workload) { - return new org.opendc.web.proto.Workload(TraceService.toUserDto(workload.trace), workload.samplingFraction); + return new org.opendc.web.proto.Workload(toDto(workload.trace), workload.samplingFraction); + } + + /** + * Convert a {@link Trace] entity into a {@link org.opendc.web.proto.Trace} DTO. + */ + public static org.opendc.web.proto.Trace toDto(Trace trace) { + return new org.opendc.web.proto.Trace(trace.id, trace.name, trace.type); } } diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/KotlinModuleCustomizer.kt b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java index 8634c8a4..c30edcbf 100644 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/util/KotlinModuleCustomizer.kt +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 AtLarge Research + * Copyright (c) 2023 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 @@ -20,19 +20,20 @@ * SOFTWARE. */ -package org.opendc.web.server.util +package org.opendc.web.server.util; -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule -import io.quarkus.jackson.ObjectMapperCustomizer -import javax.inject.Singleton +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.kotlin.KotlinModule; +import io.quarkus.jackson.ObjectMapperCustomizer; +import javax.inject.Singleton; /** * Helper class to register the Kotlin Jackson module. */ @Singleton -class KotlinModuleCustomizer : ObjectMapperCustomizer { - override fun customize(objectMapper: ObjectMapper) { - objectMapper.registerModule(KotlinModule.Builder().build()) +public final class KotlinModuleCustomizer implements ObjectMapperCustomizer { + @Override + public void customize(ObjectMapper objectMapper) { + objectMapper.registerModule(new KotlinModule.Builder().build()); } } diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt deleted file mode 100644 index 1a426095..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/OpenDCApplication.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.server - -import javax.ws.rs.core.Application - -/** - * [Application] definition for the OpenDC web API. - */ -class OpenDCApplication : Application() diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt deleted file mode 100644 index a33bd8f1..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/TraceResource.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.server.rest - -import org.opendc.web.proto.Trace -import org.opendc.web.server.service.TraceService -import javax.inject.Inject -import javax.ws.rs.GET -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the workload traces available in the OpenDC instance. - */ -@Path("/traces") -class TraceResource @Inject constructor(private val traceService: TraceService) { - /** - * Obtain all available traces. - */ - @GET - fun getAll(): List<Trace> { - return traceService.findAll() - } - - /** - * Obtain trace information by identifier. - */ - @GET - @Path("{id}") - fun get(@PathParam("id") id: String): Trace { - return traceService.findById(id) ?: throw WebApplicationException("Trace not found", 404) - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt deleted file mode 100644 index 1e9abc14..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/runner/JobResource.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.server.rest.runner - -import org.opendc.web.proto.runner.Job -import org.opendc.web.server.service.JobService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the available simulation jobs. - */ -@Path("/jobs") -@RolesAllowed("runner") -class JobResource @Inject constructor(private val jobService: JobService) { - /** - * Obtain all pending simulation jobs. - */ - @GET - fun queryPending(): List<Job> { - return jobService.listPending() - } - - /** - * Get a job by identifier. - */ - @GET - @Path("{job}") - fun get(@PathParam("job") id: Long): Job { - return jobService.findById(id) ?: throw WebApplicationException("Job not found", 404) - } - - /** - * Atomically update the state of a job. - */ - @POST - @Path("{job}") - @Transactional - fun update(@PathParam("job") id: Long, @Valid update: Job.Update): Job { - return try { - jobService.updateState(id, update.state, update.runtime, update.results) - ?: throw WebApplicationException("Job not found", 404) - } catch (e: IllegalArgumentException) { - throw WebApplicationException(e, 400) - } catch (e: IllegalStateException) { - throw WebApplicationException(e, 409) - } - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt deleted file mode 100644 index 82843a5a..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioResource.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.server.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.proto.user.Portfolio -import org.opendc.web.server.service.PortfolioService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.DELETE -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the portfolios of a project. - */ -@Path("/projects/{project}/portfolios") -@RolesAllowed("openid") -class PortfolioResource @Inject constructor( - private val portfolioService: PortfolioService, - private val identity: SecurityIdentity -) { - /** - * Get all portfolios that belong to the specified project. - */ - @GET - fun getAll(@PathParam("project") projectId: Long): List<Portfolio> { - return portfolioService.findByUser(identity.principal.name, projectId) - } - - /** - * Create a portfolio for this project. - */ - @POST - @Transactional - fun create(@PathParam("project") projectId: Long, @Valid request: Portfolio.Create): Portfolio { - return portfolioService.create(identity.principal.name, projectId, request) ?: throw WebApplicationException("Project not found", 404) - } - - /** - * Obtain a portfolio by its identifier. - */ - @GET - @Path("{portfolio}") - fun get(@PathParam("project") projectId: Long, @PathParam("portfolio") number: Int): Portfolio { - return portfolioService.findByUser(identity.principal.name, projectId, number) ?: throw WebApplicationException("Portfolio not found", 404) - } - - /** - * Delete a portfolio. - */ - @DELETE - @Path("{portfolio}") - @Transactional - fun delete(@PathParam("project") projectId: Long, @PathParam("portfolio") number: Int): Portfolio { - return portfolioService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Portfolio not found", 404) - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt deleted file mode 100644 index 82f35127..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResource.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.server.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.proto.user.Scenario -import org.opendc.web.server.service.ScenarioService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the scenarios of a portfolio. - */ -@Path("/projects/{project}/portfolios/{portfolio}/scenarios") -@RolesAllowed("openid") -class PortfolioScenarioResource @Inject constructor( - private val scenarioService: ScenarioService, - private val identity: SecurityIdentity -) { - /** - * Get all scenarios that belong to the specified portfolio. - */ - @GET - fun get(@PathParam("project") projectId: Long, @PathParam("portfolio") portfolioNumber: Int): List<Scenario> { - return scenarioService.findAll(identity.principal.name, projectId, portfolioNumber) - } - - /** - * Create a scenario for this portfolio. - */ - @POST - @Transactional - fun create(@PathParam("project") projectId: Long, @PathParam("portfolio") portfolioNumber: Int, @Valid request: Scenario.Create): Scenario { - return scenarioService.create(identity.principal.name, projectId, portfolioNumber, request) ?: throw WebApplicationException("Portfolio not found", 404) - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt deleted file mode 100644 index d12fc690..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ProjectResource.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.server.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.proto.user.Project -import org.opendc.web.server.service.ProjectService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.DELETE -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the created projects. - */ -@Path("/projects") -@RolesAllowed("openid") -class ProjectResource @Inject constructor( - private val projectService: ProjectService, - private val identity: SecurityIdentity -) { - /** - * Obtain all the projects of the current user. - */ - @GET - fun getAll(): List<Project> { - return projectService.findByUser(identity.principal.name) - } - - /** - * Create a new project for the current user. - */ - @POST - @Transactional - fun create(@Valid request: Project.Create): Project { - return projectService.create(identity.principal.name, request.name) - } - - /** - * Obtain a single project by its identifier. - */ - @GET - @Path("{project}") - fun get(@PathParam("project") id: Long): Project { - return projectService.findByUser(identity.principal.name, id) ?: throw WebApplicationException("Project not found", 404) - } - - /** - * Delete a project. - */ - @DELETE - @Path("{project}") - @Transactional - fun delete(@PathParam("project") id: Long): Project { - try { - return projectService.delete(identity.principal.name, id) ?: throw WebApplicationException("Project not found", 404) - } catch (e: IllegalArgumentException) { - throw WebApplicationException(e.message, 403) - } - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt deleted file mode 100644 index 56bb4290..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/ScenarioResource.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.server.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.proto.user.Scenario -import org.opendc.web.server.service.ScenarioService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.ws.rs.DELETE -import javax.ws.rs.GET -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the scenarios of a portfolio. - */ -@Path("/projects/{project}/scenarios") -@RolesAllowed("openid") -class ScenarioResource @Inject constructor( - private val scenarioService: ScenarioService, - private val identity: SecurityIdentity -) { - /** - * Obtain a scenario by its identifier. - */ - @GET - @Path("{scenario}") - fun get(@PathParam("project") projectId: Long, @PathParam("scenario") number: Int): Scenario { - return scenarioService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Scenario not found", 404) - } - - /** - * Delete a scenario. - */ - @DELETE - @Path("{scenario}") - @Transactional - fun delete(@PathParam("project") projectId: Long, @PathParam("scenario") number: Int): Scenario { - return scenarioService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Scenario not found", 404) - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt deleted file mode 100644 index 8eef66c8..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/TopologyResource.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.server.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.proto.user.Topology -import org.opendc.web.server.service.TopologyService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.transaction.Transactional -import javax.validation.Valid -import javax.ws.rs.DELETE -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.PUT -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException - -/** - * A resource representing the constructed datacenter topologies. - */ -@Path("/projects/{project}/topologies") -@RolesAllowed("openid") -class TopologyResource @Inject constructor( - private val topologyService: TopologyService, - private val identity: SecurityIdentity -) { - /** - * Get all topologies that belong to the specified project. - */ - @GET - fun getAll(@PathParam("project") projectId: Long): List<Topology> { - return topologyService.findAll(identity.principal.name, projectId) - } - - /** - * Create a topology for this project. - */ - @POST - @Transactional - fun create(@PathParam("project") projectId: Long, @Valid request: Topology.Create): Topology { - return topologyService.create(identity.principal.name, projectId, request) ?: throw WebApplicationException("Topology not found", 404) - } - - /** - * Obtain a topology by its number. - */ - @GET - @Path("{topology}") - fun get(@PathParam("project") projectId: Long, @PathParam("topology") number: Int): Topology { - return topologyService.findOne(identity.principal.name, projectId, number) ?: throw WebApplicationException("Topology not found", 404) - } - - /** - * Update the specified topology by its number. - */ - @PUT - @Path("{topology}") - @Transactional - fun update(@PathParam("project") projectId: Long, @PathParam("topology") number: Int, @Valid request: Topology.Update): Topology { - return topologyService.update(identity.principal.name, projectId, number, request) ?: throw WebApplicationException("Topology not found", 404) - } - - /** - * Delete the specified topology. - */ - @Path("{topology}") - @DELETE - @Transactional - fun delete(@PathParam("project") projectId: Long, @PathParam("topology") number: Int): Topology { - return topologyService.delete(identity.principal.name, projectId, number) ?: throw WebApplicationException("Topology not found", 404) - } -} diff --git a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/UserResource.kt b/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/UserResource.kt deleted file mode 100644 index d640cc08..00000000 --- a/opendc-web/opendc-web-server/src/main/kotlin/org/opendc/web/server/rest/user/UserResource.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.server.rest.user - -import io.quarkus.security.identity.SecurityIdentity -import org.opendc.web.proto.user.User -import org.opendc.web.server.service.UserService -import javax.annotation.security.RolesAllowed -import javax.inject.Inject -import javax.ws.rs.GET -import javax.ws.rs.Path - -/** - * A resource representing the active user. - */ -@Path("/users") -@RolesAllowed("openid") -class UserResource @Inject constructor(private val userService: UserService, private val identity: SecurityIdentity) { - /** - * Get the current active user data. - */ - @GET - @Path("me") - fun get(): User = userService.getUser(identity) -} diff --git a/opendc-web/opendc-web-server/src/main/resources/application-test.properties b/opendc-web/opendc-web-server/src/main/resources/application-test.properties index 338a00b9..17502b6c 100644 --- a/opendc-web/opendc-web-server/src/main/resources/application-test.properties +++ b/opendc-web/opendc-web-server/src/main/resources/application-test.properties @@ -23,6 +23,7 @@ quarkus.datasource.db-kind = h2 quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.log.sql=true quarkus.flyway.clean-at-start=true # Disable security diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/SchedulerResourceTest.kt b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/SchedulerResourceTest.java index c1460db9..feeac4d3 100644 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/SchedulerResourceTest.kt +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/SchedulerResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 AtLarge Research + * Copyright (c) 2023 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 @@ -20,29 +20,26 @@ * SOFTWARE. */ -package org.opendc.web.server.rest +package org.opendc.web.server.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 +import static io.restassured.RestAssured.when; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.Test; /** - * Test suite for [SchedulerResource] + * Test suite for {@link SchedulerResource}. */ @QuarkusTest -class SchedulerResourceTest { +@TestHTTPEndpoint(SchedulerResource.class) +public final class SchedulerResourceTest { /** * Test to verify whether we can obtain all schedulers. */ @Test - fun testGetSchedulers() { - When { - get("/schedulers") - } Then { - statusCode(200) - contentType(ContentType.JSON) - } + public void testGetSchedulers() { + when().get().then().statusCode(200).contentType(ContentType.JSON); } } diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/TraceResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/TraceResourceTest.java new file mode 100644 index 00000000..ebef3945 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/TraceResourceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 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.server.rest; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; + +import io.quarkus.panache.mock.PanacheMock; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import java.util.stream.Stream; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.opendc.web.server.model.Trace; + +/** + * Test suite for {@link TraceResource}. + */ +@QuarkusTest +@TestHTTPEndpoint(TraceResource.class) +public final class TraceResourceTest { + /** + * Set up the test environment. + */ + @BeforeEach + public void setUp() { + PanacheMock.mock(Trace.class); + } + + /** + * Test that tries to obtain all traces (empty response). + */ + @Test + public void testGetAllEmpty() { + Mockito.when(Trace.streamAll()).thenReturn(Stream.of()); + + when().get().then().statusCode(200).contentType(ContentType.JSON).body("", Matchers.empty()); + } + + /** + * Test that tries to obtain a non-existent trace. + */ + @Test + public void testGetNonExisting() { + Mockito.when(Trace.findById("bitbrains")).thenReturn(null); + + when().get("/bitbrains").then().statusCode(404).contentType(ContentType.JSON); + } + + /** + * Test that tries to obtain an existing trace. + */ + @Test + public void testGetExisting() { + Mockito.when(Trace.findById("bitbrains")).thenReturn(new Trace("bitbrains", "Bitbrains", "VM")); + + when().get("/bitbrains") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("name", equalTo("Bitbrains")); + } +} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/runner/JobResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/runner/JobResourceTest.java new file mode 100644 index 00000000..a163cd29 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/runner/JobResourceTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023 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.server.rest.runner; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.opendc.web.proto.JobState; +import org.opendc.web.proto.OperationalPhenomena; +import org.opendc.web.proto.Targets; +import org.opendc.web.proto.Trace; +import org.opendc.web.proto.Workload; +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 org.opendc.web.server.service.JobService; + +/** + * Test suite for {@link JobResource}. + */ +@QuarkusTest +@TestHTTPEndpoint(JobResource.class) +public final class JobResourceTest { + @InjectMock + private JobService jobService; + + /** + * Dummy values + */ + private final Portfolio dummyPortfolio = new Portfolio(1, 1, "test", new Targets(Set.of(), 1)); + + private final Topology dummyTopology = new Topology(1, 1, "test", List.of(), Instant.now(), Instant.now()); + private final Trace dummyTrace = new Trace("bitbrains", "Bitbrains", "vm"); + private final Scenario dummyScenario = new Scenario( + 1, + 1, + dummyPortfolio, + "test", + new Workload(dummyTrace, 1.0), + dummyTopology, + new OperationalPhenomena(false, false), + "test"); + private final Job dummyJob = new Job(1, dummyScenario, JobState.PENDING, Instant.now(), Instant.now(), 0, null); + + /** + * Test that tries to query the pending jobs without token. + */ + @Test + public void testQueryWithoutToken() { + when().get().then().statusCode(401); + } + + /** + * Test that tries to query the pending jobs for a user. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testQueryInvalidScope() { + when().get().then().statusCode(403); + } + + /** + * Test that tries to query the pending jobs for a runner. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"runner"}) + public void testQuery() { + Mockito.when(jobService.listPending()).thenReturn(List.of(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"}) + public void testGetNonExisting() { + Mockito.when(jobService.findById(1)).thenReturn(null); + + when().get("/1").then().statusCode(404).contentType(ContentType.JSON); + } + + /** + * Test that tries to obtain a job. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"runner"}) + public void testGetExisting() { + Mockito.when(jobService.findById(1)).thenReturn(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"}) + public void testUpdateNonExistent() { + Mockito.when(jobService.updateState(eq(1L), any(), anyInt(), any())).thenReturn(null); + + given().body(new Job.Update(JobState.PENDING, 0, null)) + .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"}) + public void testUpdateState() { + Mockito.when(jobService.updateState(eq(1L), any(), anyInt(), any())) + .thenReturn(new Job(1, dummyScenario, JobState.CLAIMED, Instant.now(), Instant.now(), 0, null)); + + given().body(new Job.Update(JobState.CLAIMED, 0, null)) + .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"}) + public void testUpdateInvalidInput() { + given().body("{ \"test\": \"test\" }") + .contentType(ContentType.JSON) + .when() + .post("/1") + .then() + .statusCode(400) + .contentType(ContentType.JSON); + } +} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioResourceTest.java new file mode 100644 index 00000000..cc3ac978 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioResourceTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +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 org.opendc.web.server.service.PortfolioService; + +/** + * Test suite for {@link PortfolioResource}. + */ +@QuarkusTest +@TestHTTPEndpoint(PortfolioResource.class) +public final class PortfolioResourceTest { + @InjectMock + private PortfolioService portfolioService; + + /** + * Dummy project and portfolio + */ + private final Project dummyProject = new Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER); + + private final Portfolio dummyPortfolio = + new Portfolio(1, 1, dummyProject, "test", new Targets(Set.of(), 1), List.of()); + + /** + * Test that tries to obtain the list of portfolios belonging to a project. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testGetForProject() { + Mockito.when(portfolioService.findByUser("testUser", 1)).thenReturn(List.of()); + + 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"}) + public void testCreateNonExistent() { + Mockito.when(portfolioService.create(eq("testUser"), eq(1), any())).thenReturn(null); + + given().pathParam("project", "1") + .body(new Portfolio.Create("test", new Targets(Set.of(), 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"}) + public void testCreate() { + Mockito.when(portfolioService.create(eq("testUser"), eq(1L), any())).thenReturn(dummyPortfolio); + + given().pathParam("project", "1") + .body(new Portfolio.Create("test", new Targets(Set.of(), 1))) + .contentType(ContentType.JSON) + .when() + .post() + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(1)) + .body("name", equalTo("test")); + } + + /** + * Test to create a portfolio with an empty body. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void 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"}) + public void testCreateBlankName() { + given().pathParam("project", "1") + .body(new Portfolio.Create("", new Targets(Set.of(), 1))) + .contentType(ContentType.JSON) + .when() + .post() + .then() + .statusCode(400) + .contentType(ContentType.JSON); + } + + /** + * Test that tries to obtain a portfolio without token. + */ + @Test + public void 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"}) + public void 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"}) + public void testGetNonExisting() { + Mockito.when(portfolioService.findByUser("testUser", 1, 1)).thenReturn(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"}) + public void testGetExisting() { + Mockito.when(portfolioService.findByUser("testUser", 1, 1)).thenReturn(dummyPortfolio); + + given().pathParam("project", "1") + .when() + .get("/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(1)); + } + + /** + * Test to delete a non-existent portfolio. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testDeleteNonExistent() { + Mockito.when(portfolioService.delete("testUser", 1, 1)).thenReturn(null); + + given().pathParam("project", "1").when().delete("/1").then().statusCode(404); + } + + /** + * Test to delete a portfolio. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testDelete() { + Mockito.when(portfolioService.delete("testUser", 1, 1)).thenReturn(dummyPortfolio); + + given().pathParam("project", "1") + .when() + .delete("/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } +} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.java new file mode 100644 index 00000000..8cb95a98 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.opendc.web.proto.JobState; +import org.opendc.web.proto.OperationalPhenomena; +import org.opendc.web.proto.Targets; +import org.opendc.web.proto.Trace; +import org.opendc.web.proto.Workload; +import org.opendc.web.proto.user.Job; +import org.opendc.web.proto.user.Portfolio; +import org.opendc.web.proto.user.Project; +import org.opendc.web.proto.user.ProjectRole; +import org.opendc.web.proto.user.Scenario; +import org.opendc.web.proto.user.Topology; +import org.opendc.web.server.service.ScenarioService; + +/** + * Test suite for {@link PortfolioScenarioResource}. + */ +@QuarkusTest +@TestHTTPEndpoint(PortfolioScenarioResource.class) +public final class PortfolioScenarioResourceTest { + @InjectMock + private ScenarioService scenarioService; + + /** + * Dummy values + */ + private final Project dummyProject = new Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER); + + private final Portfolio.Summary dummyPortfolio = new Portfolio.Summary(1, 1, "test", new Targets(Set.of(), 1)); + private final Job dummyJob = new Job(1, JobState.PENDING, Instant.now(), Instant.now(), null); + private final Trace dummyTrace = new Trace("bitbrains", "Bitbrains", "vm"); + private final Topology.Summary dummyTopology = new Topology.Summary(1, 1, "test", Instant.now(), Instant.now()); + private final Scenario dummyScenario = new Scenario( + 1, + 1, + dummyProject, + dummyPortfolio, + "test", + new Workload(dummyTrace, 1.0), + dummyTopology, + new OperationalPhenomena(false, false), + "test", + dummyJob); + + /** + * Test that tries to obtain a portfolio without token. + */ + @Test + public void 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"}) + public void 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"}) + public void testGet() { + Mockito.when(scenarioService.findAll("testUser", 1, 1)).thenReturn(List.of()); + + 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"}) + public void testCreateNonExistent() { + Mockito.when(scenarioService.create(eq("testUser"), eq(1L), anyInt(), any())) + .thenReturn(null); + + given().pathParam("project", "1") + .pathParam("portfolio", "1") + .body(new Scenario.Create( + "test", new Workload.Spec("test", 1.0), 1, new 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"}) + public void testCreate() { + Mockito.when(scenarioService.create(eq("testUser"), eq(1L), eq(1), any())) + .thenReturn(dummyScenario); + + given().pathParam("project", "1") + .pathParam("portfolio", "1") + .body(new Scenario.Create( + "test", new Workload.Spec("test", 1.0), 1, new OperationalPhenomena(false, false), "test")) + .contentType(ContentType.JSON) + .when() + .post() + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(1)) + .body("name", equalTo("test")); + } + + /** + * Test to create a project with an empty body. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void 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"}) + public void testCreateBlankName() { + given().pathParam("project", "1") + .pathParam("portfolio", "1") + .body(new Scenario.Create( + "", new Workload.Spec("test", 1.0), 1, new OperationalPhenomena(false, false), "test")) + .contentType(ContentType.JSON) + .when() + .post() + .then() + .statusCode(400) + .contentType(ContentType.JSON); + } +} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ProjectResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ProjectResourceTest.java new file mode 100644 index 00000000..7ca314a6 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ProjectResourceTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.opendc.web.proto.user.Project; +import org.opendc.web.proto.user.ProjectRole; +import org.opendc.web.server.service.ProjectService; + +/** + * Test suite for [ProjectResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(ProjectResource.class) +public final class ProjectResourceTest { + @InjectMock + private ProjectService projectService; + + /** + * Dummy values. + */ + private final Project dummyProject = new Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER); + + /** + * Test that tries to obtain all projects without token. + */ + @Test + public void testGetAllWithoutToken() { + when().get().then().statusCode(401); + } + + /** + * Test that tries to obtain all projects with an invalid scope. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"runner"}) + public void testGetAllWithInvalidScope() { + when().get().then().statusCode(403); + } + + /** + * Test that tries to obtain all project for a user. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testGetAll() { + Mockito.when(projectService.findByUser("testUser")).thenReturn(List.of(dummyProject)); + + 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"}) + public void testGetNonExisting() { + Mockito.when(projectService.findByUser("testUser", 1)).thenReturn(null); + + when().get("/1").then().statusCode(404).contentType(ContentType.JSON); + } + + /** + * Test that tries to obtain a job. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testGetExisting() { + Mockito.when(projectService.findByUser("testUser", 1)).thenReturn(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"}) + public void testCreate() { + Mockito.when(projectService.create("testUser", "test")).thenReturn(dummyProject); + + given().body(new 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"}) + public void 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"}) + public void testCreateBlankName() { + given().body(new 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"}) + public void testDeleteNonExistent() { + Mockito.when(projectService.delete("testUser", 1)).thenReturn(null); + + when().delete("/1").then().statusCode(404).contentType(ContentType.JSON); + } + + /** + * Test to delete a project. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testDelete() { + Mockito.when(projectService.delete("testUser", 1)).thenReturn(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"}) + public void testDeleteNonOwner() { + Mockito.when(projectService.delete("testUser", 1)) + .thenThrow(new IllegalArgumentException("User does not own project")); + + when().delete("/1").then().statusCode(403).contentType(ContentType.JSON); + } +} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ScenarioResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ScenarioResourceTest.java new file mode 100644 index 00000000..850236d6 --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ScenarioResourceTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.Instant; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.opendc.web.proto.JobState; +import org.opendc.web.proto.OperationalPhenomena; +import org.opendc.web.proto.Targets; +import org.opendc.web.proto.Trace; +import org.opendc.web.proto.Workload; +import org.opendc.web.proto.user.Job; +import org.opendc.web.proto.user.Portfolio; +import org.opendc.web.proto.user.Project; +import org.opendc.web.proto.user.ProjectRole; +import org.opendc.web.proto.user.Scenario; +import org.opendc.web.proto.user.Topology; +import org.opendc.web.server.service.ScenarioService; + +/** + * Test suite for [ScenarioResource]. + */ +@QuarkusTest +@TestHTTPEndpoint(ScenarioResource.class) +public final class ScenarioResourceTest { + @InjectMock + private ScenarioService scenarioService; + + /** + * Dummy values + */ + private final Project dummyProject = new Project(0, "test", Instant.now(), Instant.now(), ProjectRole.OWNER); + + private final Portfolio.Summary dummyPortfolio = new Portfolio.Summary(1, 1, "test", new Targets(Set.of(), 1)); + private final Job dummyJob = new Job(1, JobState.PENDING, Instant.now(), Instant.now(), null); + private final Trace dummyTrace = new Trace("bitbrains", "Bitbrains", "vm"); + private final Topology.Summary dummyTopology = new Topology.Summary(1, 1, "test", Instant.now(), Instant.now()); + private final Scenario dummyScenario = new Scenario( + 1, + 1, + dummyProject, + dummyPortfolio, + "test", + new Workload(dummyTrace, 1.0), + dummyTopology, + new OperationalPhenomena(false, false), + "test", + dummyJob); + + /** + * Test that tries to obtain a scenario without token. + */ + @Test + public void 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"}) + public void 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"}) + public void testGetNonExisting() { + Mockito.when(scenarioService.findOne("testUser", 1, 1)).thenReturn(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"}) + public void testGetExisting() { + Mockito.when(scenarioService.findOne("testUser", 1, 1)).thenReturn(dummyScenario); + + given().pathParam("project", "1") + .when() + .get("/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(1)); + } + + /** + * Test to delete a non-existent scenario. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testDeleteNonExistent() { + Mockito.when(scenarioService.delete("testUser", 1, 1)).thenReturn(null); + + given().pathParam("project", "1").when().delete("/1").then().statusCode(404); + } + + /** + * Test to delete a scenario. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testDelete() { + Mockito.when(scenarioService.delete("testUser", 1, 1)).thenReturn(dummyScenario); + + given().pathParam("project", "1") + .when() + .delete("/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } +} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/TopologyResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/TopologyResourceTest.java new file mode 100644 index 00000000..2cc6ea4b --- /dev/null +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/TopologyResourceTest.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2023 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.server.rest.user; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.opendc.web.proto.user.Project; +import org.opendc.web.proto.user.ProjectRole; +import org.opendc.web.proto.user.Topology; +import org.opendc.web.server.service.TopologyService; + +/** + * Test suite for {@link TopologyResource}. + */ +@QuarkusTest +@TestHTTPEndpoint(TopologyResource.class) +public final class TopologyResourceTest { + @InjectMock + private TopologyService topologyService; + + /** + * Dummy project and topology. + */ + private final Project dummyProject = new Project(1, "test", Instant.now(), Instant.now(), ProjectRole.OWNER); + + private final Topology dummyTopology = + new Topology(1, 1, dummyProject, "test", List.of(), Instant.now(), Instant.now()); + + /** + * Test that tries to obtain the list of topologies belonging to a project. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testGetForProject() { + Mockito.when(topologyService.findAll("testUser", 1)).thenReturn(List.of()); + + 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"}) + public void testCreateNonExistent() { + Mockito.when(topologyService.create(eq("testUser"), eq(1L), any())).thenReturn(null); + + given().pathParam("project", "1") + .body(new Topology.Create("test", List.of())) + .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"}) + public void testCreate() { + Mockito.when(topologyService.create(eq("testUser"), eq(1L), any())).thenReturn(dummyTopology); + + given().pathParam("project", "1") + .body(new Topology.Create("test", List.of())) + .contentType(ContentType.JSON) + .when() + .post() + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(1)) + .body("name", equalTo("test")); + } + + /** + * Test to create a topology with an empty body. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void 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"}) + public void testCreateBlankName() { + given().pathParam("project", "1") + .body(new Topology.Create("", List.of())) + .contentType(ContentType.JSON) + .when() + .post() + .then() + .statusCode(400) + .contentType(ContentType.JSON); + } + + /** + * Test that tries to obtain a topology without token. + */ + @Test + public void 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"}) + public void 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"}) + public void testGetNonExisting() { + Mockito.when(topologyService.findOne("testUser", 1, 1)).thenReturn(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"}) + public void testGetExisting() { + Mockito.when(topologyService.findOne("testUser", 1, 1)).thenReturn(dummyTopology); + + given().pathParam("project", "1") + .when() + .get("/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("id", equalTo(1)); + } + + /** + * Test to delete a non-existent topology. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testUpdateNonExistent() { + Mockito.when(topologyService.update(eq("testUser"), anyLong(), anyInt(), any())) + .thenReturn(null); + + given().pathParam("project", "1") + .body(new Topology.Update(List.of())) + .contentType(ContentType.JSON) + .when() + .put("/1") + .then() + .statusCode(404); + } + + /** + * Test to update a topology. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testUpdate() { + Mockito.when(topologyService.update(eq("testUser"), anyLong(), anyInt(), any())) + .thenReturn(dummyTopology); + + given().pathParam("project", "1") + .body(new Topology.Update(List.of())) + .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"}) + public void testDeleteNonExistent() { + Mockito.when(topologyService.delete("testUser", 1, 1)).thenReturn(null); + + given().pathParam("project", "1").when().delete("/1").then().statusCode(404); + } + + /** + * Test to delete a topology. + */ + @Test + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testDelete() { + Mockito.when(topologyService.delete("testUser", 1, 1)).thenReturn(dummyTopology); + + given().pathParam("project", "1") + .when() + .delete("/1") + .then() + .statusCode(200) + .contentType(ContentType.JSON); + } +} diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/UserResourceTest.kt b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/UserResourceTest.java index 36af20f4..6dcb3b4d 100644 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/UserResourceTest.kt +++ b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/UserResourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 AtLarge Research + * Copyright (c) 2023 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 @@ -20,50 +20,46 @@ * SOFTWARE. */ -package org.opendc.web.server.rest.user +package org.opendc.web.server.rest.user; -import io.quarkus.test.common.http.TestHTTPEndpoint -import io.quarkus.test.junit.QuarkusTest -import io.quarkus.test.security.TestSecurity -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.junit.jupiter.api.Test +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.Test; /** * Test suite for [UserResource]. */ @QuarkusTest -@TestHTTPEndpoint(UserResource::class) -class UserResourceTest { +@TestHTTPEndpoint(UserResource.class) +public final class UserResourceTest { /** * Test that tries to obtain the profile of the active user. */ @Test - @TestSecurity(user = "testUser", roles = ["openid"]) - fun testMe() { - When { - get("me") - } Then { - statusCode(200) - contentType(ContentType.JSON) - - body("userId", Matchers.equalTo("testUser")) - body("accounting.simulationTime", Matchers.equalTo(0)) - body("accounting.simulationTimeBudget", Matchers.greaterThan(0)) - } + @TestSecurity( + user = "testUser", + roles = {"openid"}) + public void testMe() { + when().get("me") + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("userId", equalTo("testUser")) + .body("accounting.simulationTime", equalTo(0)) + .body("accounting.simulationTimeBudget", greaterThan(0)); } /** * Test that tries to obtain the profile of the active user without authorization. */ @Test - fun testMeUnauthorized() { - When { - get("me") - } Then { - statusCode(401) - } + public void testMeUnauthorized() { + when().get("me").then().statusCode(401); } } diff --git a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt deleted file mode 100644 index 2490cf46..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/TraceResourceTest.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.server.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.proto.Trace -import org.opendc.web.server.service.TraceService - -/** - * 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-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt deleted file mode 100644 index 753b9ac4..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/runner/JobResourceTest.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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.server.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.proto.JobState -import org.opendc.web.proto.OperationalPhenomena -import org.opendc.web.proto.Targets -import org.opendc.web.proto.Trace -import org.opendc.web.proto.Workload -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 org.opendc.web.server.service.JobService -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(), 0) - - @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.listPending() } 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(), any()) } returns null - - Given { - body(Job.Update(JobState.PENDING, 0)) - 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(), any()) } returns dummyJob.copy(state = JobState.CLAIMED) - - Given { - body(Job.Update(JobState.CLAIMED, 0)) - 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-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt deleted file mode 100644 index 3ef63a51..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioResourceTest.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * 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.server.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.proto.Targets -import org.opendc.web.proto.user.Portfolio -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import org.opendc.web.server.service.PortfolioService -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.findByUser("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.findByUser("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.findByUser("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-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt deleted file mode 100644 index 676a43dc..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.kt +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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.server.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.proto.JobState -import org.opendc.web.proto.OperationalPhenomena -import org.opendc.web.proto.Targets -import org.opendc.web.proto.Trace -import org.opendc.web.proto.Workload -import org.opendc.web.proto.user.Job -import org.opendc.web.proto.user.Portfolio -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import org.opendc.web.proto.user.Scenario -import org.opendc.web.proto.user.Topology -import org.opendc.web.server.service.ScenarioService -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-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt deleted file mode 100644 index 0be56c56..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ProjectResourceTest.kt +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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.server.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.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import org.opendc.web.server.service.ProjectService -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.findByUser("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.findByUser("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.findByUser("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.create("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.delete("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.delete("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.delete("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-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt deleted file mode 100644 index 2e080971..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/ScenarioResourceTest.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.server.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.proto.JobState -import org.opendc.web.proto.OperationalPhenomena -import org.opendc.web.proto.Targets -import org.opendc.web.proto.Trace -import org.opendc.web.proto.Workload -import org.opendc.web.proto.user.Job -import org.opendc.web.proto.user.Portfolio -import org.opendc.web.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import org.opendc.web.proto.user.Scenario -import org.opendc.web.proto.user.Topology -import org.opendc.web.server.service.ScenarioService -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-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt b/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt deleted file mode 100644 index 8a542d33..00000000 --- a/opendc-web/opendc-web-server/src/test/kotlin/org/opendc/web/server/rest/user/TopologyResourceTest.kt +++ /dev/null @@ -1,304 +0,0 @@ -/* - * 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.server.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.proto.user.Project -import org.opendc.web.proto.user.ProjectRole -import org.opendc.web.proto.user.Topology -import org.opendc.web.server.service.TopologyService -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) - } - } -} |
