diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2023-01-22 00:30:51 +0000 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2023-02-01 22:01:19 +0000 |
| commit | 6927c51885bb3073b310150c4f40c64eea44a919 (patch) | |
| tree | a3da33f2e1653be23008dba15e9b3ed7044ae921 /opendc-web/opendc-web-server/src | |
| parent | e64487cb57ca75d17fe5a8a664c1e8247c7b5168 (diff) | |
refactor(web/server): Convert resources to Java
This change converts the resource classes of the OpenDC web server to
use Java, The Quarkus integration for Java is more mature and the
programming quality of experience is not that much worse.
Diffstat (limited to 'opendc-web/opendc-web-server/src')
40 files changed, 2236 insertions, 2239 deletions
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) - } - } -} |
