Skip to content
Snippets Groups Projects
Commit afe3066a authored by Zachary Sabourin's avatar Zachary Sabourin
Browse files

feat: Update how adopters are loaded + add unit testing

parent cc9360a1
Branches master
No related tags found
No related merge requests found
Showing
with 1577 additions and 222 deletions
......@@ -10,4 +10,5 @@
/static/assets/fonts/vendor/*
/static/assets/js/*
/static/assets/css/*
.vscode/
\ No newline at end of file
.vscode/
/src/test/resources/schemas
\ No newline at end of file
......@@ -125,6 +125,7 @@ pipeline {
sh '''
npm ci --no-cache
npm run build
npm run generate-json-schema
mvn package
'''
stash name: "target", includes: "target/**/*"
......
......@@ -6,8 +6,6 @@ services:
dockerfile: ./src/main/docker/Dockerfile.jvm
ports:
- "10126:8090"
volumes:
- ./config:/config
deploy:
restart_policy:
condition: on-failure
......
......@@ -7,8 +7,12 @@ dev-start:;
clean:;
mvn clean
compile-test-resources:;
npm run clean
npm run generate-json-schema
compile-assets:;
compile-assets: compile-test-resources;
npm test && npm run build
compile-java:compile-assets;
......
This diff is collapsed.
......@@ -3,7 +3,7 @@
"description": "Source code for eclipsefdn-project-adopters",
"author": "Christopher Guindon",
"license": "EPL-2.0",
"version": "0.0.1",
"version": "1.0.0",
"bugs": {
"url": "https://gitlab.eclipse.org/eclipsefdn/it/api/eclipsefdn-project-adopters/-/issues"
},
......@@ -12,12 +12,14 @@
"url": "git://gitlab.eclipse.org/eclipsefdn/it/api/eclipsefdn-project-adopters.git"
},
"scripts": {
"test": "jsonlint config/adopters.json",
"test": "jsonlint src/main/resources/adopters.json",
"build": "rm -rf src/main/resources/META-INF/resources/* && mkdir -p src/main/resources/META-INF/resources/assets && cp -R static/assets/. src/main/resources/META-INF/resources/assets/",
"postinstall": "npm run production && npm run minify_adopters",
"minify_adopters": "mkdir -p target/config && ./node_modules/json-minify/index.js config/adopters.json > target/config/adopters.json",
"production": "NODE_ENV=production webpack --progress --config=node_modules/laravel-mix/setup/webpack.config.js && npm run adopters_json && npm run test",
"adopters_json": "cp config/adopters.json static/assets/js"
"adopters_json": "cp src/main/resources/adopters.json static/assets/js",
"generate-json-schema": "node node_modules/eclipsefdn-api-support/src/openapi2schema.js -s spec/openapi.yaml -t src/test/resources",
"clean": "rm -rf src/test/resources/schemas/"
},
"dependencies": {
"chai": "^4.2.0",
......@@ -25,6 +27,7 @@
"js-yaml": "^3.13.1",
"json-minify": "^1.0.0",
"jsonlint": "^1.6.3",
"mocha": "^7.1.2"
"mocha": "^7.1.2",
"eclipsefdn-api-support": "1.0.0"
}
}
......@@ -9,23 +9,23 @@
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.parameters>true</maven.compiler.parameters>
<failsafe.useModulePath>false</failsafe.useModulePath>
<maven.compiler.release>11</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.16.12.Final</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
<auto-value.version>1.8.2</auto-value.version>
<eclipse-api-version>0.8.8</eclipse-api-version>
<sonar.sources>src/main</sonar.sources>
<sonar.tests>src/test</sonar.tests>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.jacoco.reportPaths>${project.build.directory}/jacoco-report</sonar.jacoco.reportPaths>
<sonar.coverage.jacoco.xmlReportPaths>${project.basedir}/target/jacoco-report/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath>
<auto-value.version>1.8.2</auto-value.version>
<eclipse-api-version>0.8.8</eclipse-api-version>
</properties>
<repositories>
......@@ -82,10 +82,6 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!--
Annotation preprocessors - reduce all of the boiler plate -->
......@@ -105,6 +101,23 @@
<artifactId>jsr305</artifactId>
<version>3.0.0</version>
</dependency>
<!-- Testing dependencies only -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-test-common</artifactId>
<version>${eclipse-api-version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
......@@ -113,80 +126,82 @@
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<argLine>${argLine}
-Xmx2048m</argLine>
<includes>
<include>**/*Test.java</include>
</includes>
<annotationProcessorPaths>
<path>
<groupId>
com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${auto-value.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<skip>${maven.test.skip}</skip>
<destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile>
<dataFile>
${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile>
<output>file</output>
<append>true</append>
<skipTests>
false</skipTests>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>
${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
<activation>
<property>
<name>native</name>
</property>
</activation>
</profile>
<profile>
<id>sonar-dev</id>
<build>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.6.0.1398</version>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>
${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>
org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<sonar.host.url>https://sonarqube.dev.docker</sonar.host.url>
<quarkus.package.type>
native</quarkus.package.type>
</properties>
</profile>
</profiles>
......
......@@ -32,7 +32,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/adopted_projects"
$ref: "#/components/schemas/AdoptedProjects"
500:
description: Error while retrieving data.
......@@ -53,21 +53,21 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/adopted_project"
$ref: "#/components/schemas/AdoptedProject"
500:
description: Error while retrieving data.
components:
schemas:
adopted_projects:
AdoptedProjects:
type: array
items:
$ref: "#/components/schemas/adopted_project"
adopted_project:
$ref: "#/components/schemas/AdoptedProject"
AdoptedProject:
type: object
properties:
adopters:
$ref: "#/components/schemas/adopter"
Adopters:
$ref: "#/components/schemas/Adopter"
logo:
type: string
description: The URL containing the project logo.
......@@ -80,11 +80,11 @@ components:
url:
type: string
description: The project URL under projects.eclipse.org.
adopters:
Adopters:
type: array
items:
$ref: "#/components/schemas/adopter"
adopter:
$ref: "#/components/schemas/Adopter"
Adopter:
type: object
properties:
homepage_url:
......
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
* Copyright (c) 2020, 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
* Zachary Sabourin <zachary.sabourin@ecliupse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
......@@ -76,6 +77,7 @@ public class AdoptersResource {
@GET
@Produces(MediaType.TEXT_HTML)
public Response getHomePage() {
// Sort all Wgs alphabetically
List<WorkingGroup> workingGroups = wgs.get();
workingGroups.sort(Comparator.comparing(WorkingGroup::getTitle));
return Response
......
......@@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets;
import javax.enterprise.inject.Instance;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
......@@ -40,11 +41,12 @@ public class NotFoundMapper implements ExceptionMapper<NotFoundException> {
@Override
public Response toResponse(NotFoundException exception) {
// Attempt to serve the 404 template page first if possible
try (InputStream is = this.getClass().getResourceAsStream(errorPageLoc.get())) {
return Response.status(404).entity(IOUtils.toString(is, StandardCharsets.UTF_8)).build();
return Response.status(Status.NOT_FOUND).entity(IOUtils.toString(is, StandardCharsets.UTF_8)).build();
} catch (IOException e) {
LOGGER.error("Unable to read in error page at location {}", errorPageLoc.get(), e);
}
return Response.status(404).build();
return Response.status(Status.NOT_FOUND).build();
}
}
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
* Copyright (c) 2020, 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
* Zachary Sabourin <zachary.sabourin@ecliupse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
......@@ -21,7 +22,7 @@ import org.eclipsefoundation.efservices.api.models.Project;
/**
* Interface for service to provide information about project adopters.
*
* @author Martin Lowe
* @author Martin Lowe, Zachary Sabourin
*
*/
public interface AdopterService {
......
/*********************************************************************
* Copyright (c) 2020 Eclipse Foundation.
* Copyright (c) 2020, 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
* Zachary Sabourin <zachary.sabourin@ecliupse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.adopters.service.impl;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
......@@ -53,7 +40,7 @@ import io.quarkus.runtime.Startup;
* Retrieves the adopters information from the filesystem on service start, and
* provides copies to requesting callers.
*
* @author Martin Lowe
* @author Martin Lowe, Zachary Sabourin
*
*/
@Startup
......@@ -61,146 +48,44 @@ import io.quarkus.runtime.Startup;
public class DefaultAdopterService implements AdopterService {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAdopterService.class);
@ConfigProperty(name = "eclipse.project-adopters.adopters.filepath", defaultValue = "/tmp/quarkus/adopters.json")
Path adoptersFilepath;
@ConfigProperty(name = "eclipse.project-adopters.filepath", defaultValue = "adopters.json")
String adoptersFilepath;
@Inject
ObjectMapper objectMapper;
/**
* All updates to the adopters list should be done through the setAdopters
* method to ensure thread safety
*/
private List<Adopter> adopters;
private WatchService watcher;
private Thread watchThread;
@PostConstruct
public void init() throws IOException {
setAdopters(Collections.emptyList());
// attempt to ensure that the base directory exists for watching
Path parentDir = adoptersFilepath.getParent();
if (!Files.exists(parentDir)) {
Files.createDirectory(parentDir);
}
// read in the initial file if it exists
if (Files.exists(adoptersFilepath)) {
LOGGER.debug("Found an adopters file at path {}, reading in", adoptersFilepath);
List<Adopter> initialAdopters = readInAdopters(adoptersFilepath, objectMapper);
if (initialAdopters != null) {
setAdopters(initialAdopters);
}
}
// create and set the watch service
this.watcher = FileSystems.getDefault().newWatchService();
// register the adopter location to watch
LOGGER.debug("Registering file watcher on directory {}", parentDir);
parentDir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
// start file watching on a new thread
this.watchThread = new Thread(this::update);
this.watchThread.start();
}
@PreDestroy
public void shutdown() {
// stop the watch thread from running
this.watchThread.interrupt();
}
/**
* Update loop, will loop until shutdown is triggered or the service is stopped.
*
* @throws IOException
*/
@SuppressWarnings("unchecked")
private void update() {
while (true) {
WatchKey key = null;
try {
key = watcher.take();
// if there was an error while watching for changes, try again
if (key == null) {
continue;
}
// pass a filtered list of events to be processed
key.pollEvents().stream().filter(e -> e.kind() != OVERFLOW && e.kind().type() == Path.class)
.map(e -> (WatchEvent<Path>) e)
.filter(e -> ((Path) e.context()).getFileName().equals(adoptersFilepath.getFileName()))
.forEach(this::processEvent);
} catch (@SuppressWarnings("java:S2142") InterruptedException e) {
LOGGER.debug("Ending watch services, as thread was interrupted and closing");
try {
this.watcher.close();
} catch (IOException e1) {
LOGGER.error("Error while closing the watch service", e1);
}
break;
} finally {
// return the key to the watch service
if (key != null && key.isValid()) {
key.reset();
}
}
// Attempt to load file on startup. Failing if there is an issue
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(adoptersFilepath)) {
AdopterList adopterList = objectMapper.readValue(is, AdopterList.class);
this.adopters = new ArrayList<>(adopterList.getAdopters());
LOGGER.info("Initialized {} adopters", adopters.size());
}
}
private void processEvent(WatchEvent<Path> event) {
// clear the adopter list if the event is a deletion event
if (event.kind() == ENTRY_DELETE) {
LOGGER.debug("Detected the deletion of adopters file, emptying array");
setAdopters(Collections.emptyList());
} else {
// indicates an update or created file on the given
// retrieve the new adopters and set them if the read operation was successful
List<Adopter> newAdopters = readInAdopters(adoptersFilepath, objectMapper);
if (newAdopters != null) {
setAdopters(newAdopters);
}
}
}
private static List<Adopter> readInAdopters(Path adoptersPath, ObjectMapper mapper) {
LOGGER.debug("Detected an update for adopters file, reading in {}", adoptersPath);
// get a json processor, and read in the file
if (Files.exists(adoptersPath)) {
try (InputStream is = new BufferedInputStream(Files.newInputStream(adoptersPath))) {
AdopterList al = mapper.readValue(is, AdopterList.class);
return al.getAdopters();
} catch (IOException e) {
LOGGER.warn("Error reading file at path: {}\n", adoptersPath, e);
}
} else {
LOGGER.error("Bad file was passed for adopters update, not reading in file: {}", adoptersPath);
}
// empty list is valid state, so return null to represent error/no update
return null;
}
@Override
public List<Adopter> getAdopters() {
synchronized (this) {
return new ArrayList<>(adopters);
}
}
public void setAdopters(List<Adopter> adopters) {
synchronized (this) {
this.adopters = new ArrayList<>(adopters);
}
// Return all adopters loaded from file
return new ArrayList<>(adopters);
}
@Override
public List<AdoptedProject> getAdoptedProjects(List<Project> projects) {
// For each project, we create an AdoptedProject entity with all relevant adopters
return projects.stream().map(this::getAdoptedProject).filter(a -> !a.getAdopters().isEmpty())
.collect(Collectors.toList());
}
/**
* Creates an AdoptedProject entity using the given project information. Adds
* all project adopters that match the given project's id.
*
* @param p The given project
* @return A constructed AdoptedProject entity with all relevant information.
*/
private AdoptedProject getAdoptedProject(Project p) {
return AdoptedProject.builder()
.setProjectId(p.getProjectId())
......@@ -211,5 +96,4 @@ public class DefaultAdopterService implements AdopterService {
.sorted(Comparator.comparing(a -> a.getName().toLowerCase())).collect(Collectors.toList()))
.build();
}
}
org.eclipsefoundation.adopters.config.PathConverter
\ No newline at end of file
File moved
......@@ -16,12 +16,15 @@ wg-api/mp-rest/url=https://api.eclipse.org
wg-api/mp-rest/scope=javax.inject.Singleton
wg-api/mp-rest/followRedirects=true
## Adopters settings
eclipse.project-adopters.adopters.filepath=/config/adopters.json
%dev.eclipse.project-adopters.adopters.filepath=/tmp/config/adopters.json
## projects loading-cache configs
eclipse.cache.loading."projects".start-at-boot=true
eclipse.cache.loading."projects".runtime-boot-key=all
eclipse.cache.loading."projects".timeout=10
eclipse.cache.loading."projects".refresh-after=PT1H
## WG loading-cache configs
eclipse.cache.loading."working-groups".start-at-boot=true
eclipse.cache.loading."working-groups".runtime-boot-key=all
eclipse.cache.loading."working-groups".timeout=10
eclipse.cache.loading."working-groups".refresh-after=PT1H
/*********************************************************************
* Copyright (c) 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.adopters.resources;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import org.eclipsefoundation.adopters.model.AdoptedProject;
import org.eclipsefoundation.adopters.test.helpers.SchemaNamespaceHelper;
import org.eclipsefoundation.testing.helpers.TestCaseHelper;
import org.eclipsefoundation.testing.models.EndpointTestBuilder;
import org.eclipsefoundation.testing.models.EndpointTestCase;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import io.restassured.response.ValidatableResponse;
@QuarkusTest
class AdoptersResourceTest {
private static final String BASE_URL = "";
private static final String HOW_TO_BE_LISTED = "/how-to-be-listed-as-an-adopter";
private static final String WG_SUB_PAGE_URL = "/project-adopters/{wg}";
private static final String ADOPTED_PROJECTS_URL = "/projects";
private static final String ADOPTED_PROJECT_BY_WG_URL = ADOPTED_PROJECTS_URL + "?working_group={param}";
private static final String SINGLE_PROJECT_URL = ADOPTED_PROJECTS_URL + "/{projectId}";
private static final EndpointTestCase GET_ALL_SUCCESS = TestCaseHelper.buildSuccessCase(ADOPTED_PROJECTS_URL,
new String[] {}, SchemaNamespaceHelper.ADOPTED_PROJECTS_SCHEMA_PATH);
private static final EndpointTestCase GET_ALL_BY_WG_SUCCESS = TestCaseHelper.buildSuccessCase(
ADOPTED_PROJECT_BY_WG_URL, new String[] { "sample-wg" },
SchemaNamespaceHelper.ADOPTED_PROJECTS_SCHEMA_PATH);
private static final EndpointTestCase GET_ALL_BY_PROJECT_SUCCESS = TestCaseHelper.buildSuccessCase(
SINGLE_PROJECT_URL, new String[] { "sample.proj" }, SchemaNamespaceHelper.ADOPTED_PROJECTS_SCHEMA_PATH);
@Inject
ObjectMapper om;
/*
* GET /adopters
*/
@Test
void getBaseTemplatePage_success() {
EndpointTestBuilder.from(EndpointTestCase.builder().setPath(BASE_URL).setStatusCode(200)
.setResponseContentType(ContentType.HTML).build()).doGet().andCheckFormat().run();
}
/*
* GET /adopters/how-to-be-listed-as-an-adopter
*/
@Test
void getHowToTemplatePage_success() {
EndpointTestBuilder.from(EndpointTestCase.builder().setPath(HOW_TO_BE_LISTED).setStatusCode(200)
.setResponseContentType(ContentType.HTML).build()).doGet()
.run();
}
/*
* GET /adopters/project-adopters/{wg}
*/
@Test
void getSubTemplatePage_success() {
EndpointTestBuilder.from(EndpointTestCase.builder().setPath(WG_SUB_PAGE_URL).setStatusCode(200)
.setResponseContentType(ContentType.HTML).setParams(Optional.of(new String[] { "sample-wg" })).build())
.doGet().run();
}
@Test
void getSubTemplatePage_success_invalidWG() {
EndpointTestBuilder.from(EndpointTestCase.builder().setPath(WG_SUB_PAGE_URL).setStatusCode(200)
.setResponseContentType(ContentType.HTML).setParams(Optional.of(new String[] { "invalid-wg" })).build())
.doGet().run();
}
/*
* GET /adopters/projects
*/
@Test
void getAdoptedProjects_success() {
ValidatableResponse response = EndpointTestBuilder.from(GET_ALL_SUCCESS).doGet().run();
List<AdoptedProject> adoptedProjects = parseJsonResponse(response.extract().body().asString());
Assertions.assertTrue(!adoptedProjects.isEmpty(), "Results are expected");
}
@Test
void getAdoptedProjects_success_validateSchema() {
EndpointTestBuilder.from(GET_ALL_SUCCESS).doGet().andCheckSchema().run();
}
@Test
void getAdoptedProjects_success_validateFormat() {
EndpointTestBuilder.from(GET_ALL_SUCCESS).doGet().andCheckFormat().run();
}
/*
* GET /adopters/projects?working_group={param}
*/
@Test
void getAdoptedProjectsByWG_success() {
ValidatableResponse response = EndpointTestBuilder.from(GET_ALL_BY_WG_SUCCESS).doGet().run();
List<AdoptedProject> adoptedProjects = parseJsonResponse(response.extract().body().asString());
Assertions.assertTrue(!adoptedProjects.isEmpty(), "Results are expected");
}
@Test
void getAdoptedProjectsByWG_success_validateSchema() {
EndpointTestBuilder.from(GET_ALL_BY_WG_SUCCESS).doGet().andCheckSchema().run();
}
@Test
void getAdoptedProjectsByWG_success_validateFormat() {
EndpointTestBuilder.from(GET_ALL_BY_WG_SUCCESS).doGet().andCheckFormat().run();
}
@Test
void getAdoptedProjects_success_invalidWG() {
// Invalid working group ids return a 200 with an empty array
ValidatableResponse response = EndpointTestBuilder
.from(TestCaseHelper.buildSuccessCase(ADOPTED_PROJECT_BY_WG_URL, new String[] { "invalid-wg" },
SchemaNamespaceHelper.ADOPTED_PROJECTS_SCHEMA_PATH))
.doGet().run();
List<AdoptedProject> adoptedProjects = parseJsonResponse(response.extract().body().asString());
Assertions.assertTrue(adoptedProjects.isEmpty(), "Results are not expected");
}
/*
* GET /adopters/projects/{projectId}
*/
@Test
void getAdoptedProjectsByProject_success() {
ValidatableResponse response = EndpointTestBuilder.from(GET_ALL_BY_PROJECT_SUCCESS).doGet().run();
List<AdoptedProject> adoptedProjects = parseJsonResponse(response.extract().body().asString());
Assertions.assertTrue(!adoptedProjects.isEmpty(), "Results are expected");
}
@Test
void getAdoptedProjectsByProject_success_validateSchema() {
EndpointTestBuilder.from(GET_ALL_BY_PROJECT_SUCCESS).doGet().andCheckSchema().run();
}
@Test
void getAdoptedProjectsByProject_success_validateFormat() {
EndpointTestBuilder.from(GET_ALL_BY_PROJECT_SUCCESS).doGet().andCheckFormat().run();
}
@Test
void getAdoptedProjects_success_invalidProject() {
// Invalid project ids return a 200 with an empty array
ValidatableResponse response = EndpointTestBuilder
.from(TestCaseHelper.buildSuccessCase(SINGLE_PROJECT_URL, new String[] { "invalid.proj" },
SchemaNamespaceHelper.ADOPTED_PROJECTS_SCHEMA_PATH))
.doGet().run();
List<AdoptedProject> adoptedProjects = parseJsonResponse(response.extract().body().asString());
Assertions.assertTrue(adoptedProjects.isEmpty(), "Results are not expected");
}
private List<AdoptedProject> parseJsonResponse(String adoptedProjectBody) {
try {
return om.readerForListOf(AdoptedProject.class).readValue(adoptedProjectBody);
} catch (Exception e) {
throw new RuntimeException();
}
}
}
/*********************************************************************
* Copyright (c) 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.adopters.test.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
import org.eclipsefoundation.efservices.api.ProjectsAPI;
import org.eclipsefoundation.efservices.api.models.GitlabProject;
import org.eclipsefoundation.efservices.api.models.InterestGroup;
import org.eclipsefoundation.efservices.api.models.InterestGroup.Descriptor;
import org.eclipsefoundation.efservices.api.models.InterestGroup.InterestGroupParticipant;
import org.eclipsefoundation.efservices.api.models.InterestGroup.Organization;
import org.eclipsefoundation.efservices.api.models.InterestGroup.Resource;
import org.eclipsefoundation.efservices.api.models.Project;
import org.eclipsefoundation.efservices.api.models.Project.Collaboration;
import org.eclipsefoundation.efservices.api.models.Project.GithubProject;
import org.eclipsefoundation.efservices.api.models.Project.ProjectParticipant;
import org.eclipsefoundation.efservices.api.models.Project.Repo;
import io.quarkus.test.Mock;
@Mock
@RestClient
@ApplicationScoped
public class MockProjectsAPI implements ProjectsAPI {
private List<Project> projects;
public MockProjectsAPI() {
this.projects = new ArrayList<>();
// sample repos
Repo r1 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/sample").build();
Repo r2 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/test").build();
Repo r3 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/prototype.git").build();
Repo r4 = Repo.builder().setUrl("http://www.github.com/eclipsefdn/tck-proto").build();
Repo r5 = Repo.builder().setUrl("/gitroot/sample/gerrit.project.git").build();
Repo r6 = Repo.builder().setUrl("/gitroot/sample/gerrit.other-project").build();
// sample users
ProjectParticipant u1 = ProjectParticipant
.builder()
.setUrl("")
.setUsername("da_wizz")
.setFullName("da_wizz")
.build();
ProjectParticipant u2 = ProjectParticipant
.builder()
.setUrl("")
.setUsername("grunter")
.setFullName("grunter")
.build();
this.projects
.add(Project
.builder()
.setName("Sample project")
.setProjectId("sample.proj")
.setSpecProjectWorkingGroup(Collections.emptyMap())
.setGithubRepos(Arrays.asList(r1, r2))
.setGerritRepos(Arrays.asList(r5))
.setCommitters(Arrays.asList(u1, u2))
.setProjectLeads(Collections.emptyList())
.setGitlab(GitlabProject
.builder()
.setIgnoredSubGroups(Collections.emptyList())
.setProjectGroup("")
.build())
.setShortProjectId("sample.proj")
.setSummary("summary")
.setUrl("project.url.com")
.setWebsiteUrl("someproject.com")
.setWebsiteRepo(Collections.emptyList())
.setLogo("logoUrl.com")
.setTags(Collections.emptyList())
.setGithub(GithubProject
.builder()
.setOrg("org name")
.setIgnoredRepos(Collections.emptyList())
.build())
.setContributors(Collections.emptyList())
.setWorkingGroups(Collections.emptyList())
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("eclipse")
.build());
this.projects
.add(Project
.builder()
.setName("Prototype thing")
.setProjectId("sample.proto")
.setSpecProjectWorkingGroup(Collections.emptyMap())
.setGithubRepos(Arrays.asList(r3))
.setGerritRepos(Arrays.asList(r6))
.setCommitters(Arrays.asList(u2))
.setProjectLeads(Collections.emptyList())
.setGitlab(GitlabProject
.builder()
.setIgnoredSubGroups(Collections.emptyList())
.setProjectGroup("eclipse/dash-second")
.build())
.setShortProjectId("sample.proto")
.setWebsiteUrl("someproject.com")
.setSummary("summary")
.setUrl("project.url.com")
.setWebsiteRepo(Collections.emptyList())
.setLogo("logoUrl.com")
.setTags(Collections.emptyList())
.setGithub(GithubProject
.builder()
.setOrg("org name")
.setIgnoredRepos(Collections.emptyList())
.build())
.setContributors(Collections.emptyList())
.setWorkingGroups(
Arrays.asList(Collaboration.builder().setName("Sample WG").setId("sample-wg").build()))
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("eclipse")
.build());
this.projects
.add(Project
.builder()
.setName("Research project")
.setProjectId("research.proj")
.setSpecProjectWorkingGroup(Collections.emptyMap())
.setGithubRepos(Arrays.asList(r4))
.setGerritRepos(Collections.emptyList())
.setGitlab(GitlabProject
.builder()
.setIgnoredSubGroups(Arrays.asList("eclipse/dash/mirror"))
.setProjectGroup("eclipse/dash")
.build())
.setCommitters(Arrays.asList(u1, u2))
.setProjectLeads(Collections.emptyList())
.setShortProjectId("research.proj")
.setSummary("summary")
.setUrl("project.url.com")
.setWebsiteUrl("someproject.com")
.setWebsiteRepo(Collections.emptyList())
.setLogo("logoUrl.com")
.setTags(Collections.emptyList())
.setGithub(GithubProject
.builder()
.setOrg("org name")
.setIgnoredRepos(Collections.emptyList())
.build())
.setContributors(Collections.emptyList())
.setWorkingGroups(Collections.emptyList())
.setIndustryCollaborations(Collections.emptyList())
.setReleases(Collections.emptyList())
.setTopLevelProject("eclipse")
.build());
}
@Override
public Response getProjects(BaseAPIParameters params, int isSpecProject) {
if (isSpecProject == 1) {
return Response
.ok(projects.stream().filter(p -> p.getSpecWorkingGroup().isPresent()).collect(Collectors.toList()))
.build();
}
return Response.ok(projects).build();
}
@Override
public Response getInterestGroups(BaseAPIParameters params) {
return Response
.ok(Arrays
.asList(InterestGroup
.builder()
.setProjectId("foundation-internal.ig.mittens")
.setId("1")
.setLogo("")
.setState("active")
.setTitle("Magical IG Tributed To Eclipse News Sources")
.setDescription(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
.setScope(Descriptor.builder().setFull("Sample").setSummary("Sample").build())
.setGitlab(GitlabProject
.builder()
.setIgnoredSubGroups(Collections.emptyList())
.setProjectGroup("eclipse-ig/mittens")
.build())
.setLeads(Arrays
.asList(InterestGroupParticipant
.builder()
.setUrl("https://api.eclipse.org/account/profile/zacharysabourin")
.setUsername("zacharysabourin")
.setFullName("zachary sabourin")
.setOrganization(Organization
.builder()
.setDocuments(Collections.emptyMap())
.setId("id")
.setName("org")
.build())
.build()))
.setParticipants(Arrays
.asList(InterestGroupParticipant
.builder()
.setUrl("https://api.eclipse.org/account/profile/skilpatrick")
.setUsername("skilpatrick")
.setFullName("Skil Patrick")
.setOrganization(Organization
.builder()
.setDocuments(Collections.emptyMap())
.setId("id")
.setName("org")
.build())
.build()))
.setShortProjectId("mittens")
.setResources(Resource.builder().setMembers("members").setWebsite("google.com").build())
.setMailingList("mailinglist.com")
.build()))
.build();
}
}
\ No newline at end of file
/*********************************************************************
* Copyright (c) 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.adopters.test.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
import org.eclipsefoundation.efservices.api.WorkingGroupsAPI;
import org.eclipsefoundation.efservices.api.models.WorkingGroup;
import org.eclipsefoundation.efservices.api.models.WorkingGroup.WorkingGroupParticipationAgreement;
import org.eclipsefoundation.efservices.api.models.WorkingGroup.WorkingGroupParticipationAgreements;
import org.eclipsefoundation.efservices.api.models.WorkingGroup.WorkingGroupParticipationLevel;
import org.eclipsefoundation.efservices.api.models.WorkingGroup.WorkingGroupResources;
import io.quarkus.test.Mock;
@Mock
@RestClient
@ApplicationScoped
public class MockWorkingGroupAPI implements WorkingGroupsAPI {
private List<WorkingGroup> wgs;
public MockWorkingGroupAPI() {
this.wgs = new ArrayList<>();
this.wgs.addAll(Arrays.asList(WorkingGroup
.builder()
.setAlias("sample-wg")
.setDescription("")
.setLevels(Arrays.asList(WorkingGroupParticipationLevel.builder().setDescription("sample")
.setRelation("WGSAMP").build()))
.setLogo("")
.setParentOrganization("eclipse")
.setResources(WorkingGroupResources
.builder()
.setCharter("")
.setContactForm("")
.setMembers("")
.setSponsorship("")
.setWebsite("")
.setParticipationAgreements(WorkingGroupParticipationAgreements
.builder()
.setIndividual(WorkingGroupParticipationAgreement
.builder()
.setDocumentId("sample-wg-iwgpa")
.setPdf("sample-wg-iwgpa.pdf")
.build())
.setOrganization(WorkingGroupParticipationAgreement
.builder()
.setDocumentId("sample-wg-owgpa")
.setPdf("sample-wg-owgpa.pdf")
.build())
.build())
.build())
.setStatus("active")
.setTitle("Sample WG")
.build()));
}
@Override
public Response get(BaseAPIParameters baseParams) {
return Response.ok(wgs).build();
}
@Override
public Response getAllByStatuses(BaseAPIParameters baseParams, List<String> statuses) {
return Response.ok(wgs.stream().filter(wg -> statuses.contains(wg.getStatus())).collect(Collectors.toList()))
.build();
}
}
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
* Copyright (c) 2023 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.adopters.config;
package org.eclipsefoundation.adopters.test.helpers;
import java.nio.file.Path;
import java.nio.file.Paths;
public class SchemaNamespaceHelper {
public static final String BASE_SCHEMA_PATH = "schemas/";
public static final String BASE_SCHEMA_PATH_SUFFIX = "-schema.json";
import org.eclipse.microprofile.config.spi.Converter;
/**
* Provides a way to bind NIO paths to MP property values.
*
* @author Martin Lowe
*
*/
public class PathConverter implements Converter<Path> {
private static final long serialVersionUID = 1L;
@Override
public Path convert(String value) {
return Paths.get(value);
}
}
\ No newline at end of file
public static final String ADOPTED_PROJECT_SCHEMA_PATH = BASE_SCHEMA_PATH + "adopted-project" + BASE_SCHEMA_PATH_SUFFIX;
public static final String ADOPTED_PROJECTS_SCHEMA_PATH = BASE_SCHEMA_PATH + "adopted-projects" + BASE_SCHEMA_PATH_SUFFIX;
public static final String ADOPTER_SCHEMA_PATH = BASE_SCHEMA_PATH + "adopter" + BASE_SCHEMA_PATH_SUFFIX;
public static final String ADOPTERS = BASE_SCHEMA_PATH + "adopters" + BASE_SCHEMA_PATH_SUFFIX;
}
{
"adopters": [
{
"name": "Test Adopter",
"homepage_url": "google.com",
"logo": "logo.png",
"logo_white": "logo-white.png",
"projects": [
"sample.proj"
]
},
{
"name": "Sample Corp",
"homepage_url": "google.com",
"logo": "logo.png",
"logo_white": "logo-white.png",
"projects": [
"sample.proj"
]
},
{
"name": "Adopters Inc.",
"homepage_url": "google.com",
"logo": "logo.png",
"logo_white": "logo-white.png",
"projects": [
"sample.proto"
]
},
{
"name": "Technology User",
"homepage_url": "google.com",
"logo": "logo.png",
"logo_white": "logo-white.png",
"projects": [
"sample.proto",
"research.proj"
]
}
]
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment