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

Merge branch 'zacharysabourin/main/17' into 'main'

feat: Impl profile endpoint + tests

Closes #17

See merge request !6
parents 4b064c7c 0084f28b
No related branches found
No related tags found
1 merge request!6feat: Impl profile endpoint + tests
Pipeline #16629 passed
......@@ -104,6 +104,26 @@ paths:
500:
description: Error while retrieving data
/profile:
get:
tags:
- Profile
summary: User profile
description: Fetch the profile data for the current user.
security:
- OAuth2: [openvsx_publisher_agreement, openid, profile]
responses:
200:
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/EfUser"
403:
description: Invalid credentials
500:
description: Error while retrieving data
components:
securitySchemes:
OAuth2:
......@@ -177,3 +197,150 @@ components:
- type: string
- type: "null"
description: The URL
EfUser:
type: object
properties:
uid:
type: integer
description: The unique user id.
name:
type: string
description: The user's ef username name.
picture:
type: string
description: URL of the user's picture.
mail:
oneOf:
- type: string
- type: "null"
description: The user's email.
eca:
oneOf:
- $ref: "#/components/schemas/Eca"
- type: "null"
is_committer:
oneOf:
- type: boolean
- type: "null"
description: User's committer status.
friends:
oneOf:
- $ref: "#/components/schemas/Friends"
- type: "null"
first_name:
type: string
description: The user's first name.
last_name:
type: string
description: The user's last name.
publisher_agreements:
type: object
propertyNames:
description: The publisher agreement name.
additionalProperties:
$ref: "#/components/schemas/ProfilePublisherAgreement"
github_handle:
type: string
description: The user's Github handle.
twitter_handle:
type: string
description: The user's Twitter handle.
org:
type: string
description: The user's organization.
org_id:
oneOf:
- type: string
- type: "null"
description: The user's organization id.
job_title:
type: string
description: The user's job title at the organization.
website:
type: string
description: The user's website.
country:
$ref: "#/components/schemas/Country"
bio:
oneOf:
- type: string
- type: "null"
description: The user's account bio
interests:
type: array
items:
type: string
description: A list of the user's interests.
working_groups_interests:
type: array
items:
type: string
description: A list of the user's working group interests.
forums_url:
oneOf:
- type: string
- type: "null"
description: The user's forum URL.
gerrit_url:
oneOf:
- type: string
- type: "null"
description: The user's Gerrit URL.
projects_url:
oneOf:
- type: string
- type: "null"
description: The user's projects URL.
mailinglist_url:
oneOf:
- type: string
- type: "null"
description: The user's mailing list URL.
mpc_favorites_url:
oneOf:
- type: string
- type: "null"
description: The user's Marketplace favorites URL.
Eca:
type: object
properties:
signed:
type: boolean
description: Flag determining whether user has signed ECA.
can_contribute_spec_project:
type: boolean
description: Flag determining whether users can contribute to a spec project.
example:
signed: true
can_contribute_spec_project: true
Friends:
type: object
properties:
friend_id:
type: integer
description: The friend's userid.
example:
friend_id: 6104
Country:
type: object
properties:
code:
type: string
description: The country code.
name:
type: string
description: The country name.
example:
code: CA
name: Canada
ProfilePublisherAgreement:
type: object
properties:
version:
type: string
description: the agreement document version.
......@@ -11,6 +11,11 @@
**********************************************************************/
package org.eclipsefoundation.openvsx.api.models;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.auto.value.AutoValue;
......@@ -23,8 +28,66 @@ public abstract class EfUser {
public abstract String getName();
public abstract String getPicture();
@Nullable
public abstract String getMail();
@Nullable
public abstract Eca getEca();
@Nullable
public abstract Boolean getIsCommitter();
@Nullable
public abstract FriendsData getFriends();
public abstract String getFirstName();
public abstract String getLastName();
public abstract Map<String, PublisherAgreement> getPublisherAgreements();
@Nullable
public abstract String getGithubHandle();
public abstract String getTwitterHandle();
public abstract String getOrg();
@Nullable
public abstract String getOrgId();
public abstract String getJobTitle();
public abstract String getWebsite();
public abstract Country getCountry();
@Nullable
public abstract String getBio();
public abstract List<String> getInterests();
public abstract List<String> getWorkingGroupsInterests();
@Nullable
public abstract String getForumsUrl();
@Nullable
public abstract String getProjectsUrl();
@Nullable
public abstract String getGerritUrl();
@Nullable
public abstract String getMailinglistUrl();
@Nullable
public abstract String getMpcFavoritesUrl();
public abstract Builder toBuilder();
public static Builder builder() {
return new AutoValue_EfUser.Builder();
}
......@@ -37,8 +100,142 @@ public abstract class EfUser {
public abstract Builder setName(String name);
public abstract Builder setGithubHandle(String handle);
public abstract Builder setPicture(String picture);
public abstract Builder setMail(@Nullable String mail);
public abstract Builder setEca(@Nullable Eca eca);
public abstract Builder setIsCommitter(@Nullable Boolean isCommitter);
public abstract Builder setFriends(@Nullable FriendsData friends);
public abstract Builder setFirstName(String fName);
public abstract Builder setLastName(String lName);
public abstract Builder setPublisherAgreements(Map<String, PublisherAgreement> agreements);
public abstract Builder setGithubHandle(@Nullable String handle);
public abstract Builder setTwitterHandle(String handle);
public abstract Builder setOrg(String org);
public abstract Builder setOrgId(@Nullable String id);
public abstract Builder setJobTitle(String title);
public abstract Builder setWebsite(String website);
public abstract Builder setCountry(Country country);
public abstract Builder setBio(@Nullable String bio);
public abstract Builder setInterests(List<String> interests);
public abstract Builder setWorkingGroupsInterests(List<String> interests);
public abstract Builder setForumsUrl(@Nullable String url);
public abstract Builder setProjectsUrl(@Nullable String url);
public abstract Builder setGerritUrl(@Nullable String url);
public abstract Builder setMailinglistUrl(@Nullable String url);
public abstract Builder setMpcFavoritesUrl(@Nullable String url);
public abstract EfUser build();
}
@AutoValue
@JsonDeserialize(builder = AutoValue_EfUser_Eca.Builder.class)
public abstract static class Eca {
public abstract boolean getSigned();
public abstract boolean getCanContributeSpecProject();
public static Builder builder() {
return new AutoValue_EfUser_Eca.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setSigned(boolean signed);
public abstract Builder setCanContributeSpecProject(boolean canContribute);
public abstract Eca build();
}
}
@AutoValue
@JsonDeserialize(builder = AutoValue_EfUser_FriendsData.Builder.class)
public abstract static class FriendsData {
public abstract int getFriendId();
public static Builder builder() {
return new AutoValue_EfUser_FriendsData.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setFriendId(int id);
public abstract FriendsData build();
}
}
@AutoValue
@JsonDeserialize(builder = AutoValue_EfUser_Country.Builder.class)
public abstract static class Country {
@Nullable
public abstract String getCode();
@Nullable
public abstract String getName();
public static Builder builder() {
return new AutoValue_EfUser_Country.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setCode(@Nullable String code);
public abstract Builder setName(@Nullable String name);
public abstract Country build();
}
}
@AutoValue
@JsonDeserialize(builder = AutoValue_EfUser_PublisherAgreement.Builder.class)
public abstract static class PublisherAgreement {
public abstract String getVersion();
public static Builder builder() {
return new AutoValue_EfUser_PublisherAgreement.Builder();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "set")
public abstract static class Builder {
public abstract Builder setVersion(String version);
public abstract PublisherAgreement build();
}
}
}
package org.eclipsefoundation.openvsx.resources;
import java.util.Optional;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.exception.FinalForbiddenException;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.openvsx.api.EclipseAPI;
import org.eclipsefoundation.openvsx.api.models.DrupalUserInfo;
import org.eclipsefoundation.openvsx.api.models.EfUser;
import org.eclipsefoundation.openvsx.request.OAuthFilter;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
@Path("profile")
public class ProfileResource {
@Context
HttpServletRequest request;
@Inject
@RestClient
EclipseAPI eclipseAPI;
@Inject
CachingService cache;
@GET
public Response getProfileInfo() {
String username = getTokenUser().getName();
Optional<EfUser> result = cache.get(username, new MultivaluedMapImpl<>(), EfUser.class,
() -> eclipseAPI.getUserByUsername(username));
if (result.isEmpty()) {
throw new NotFoundException(String.format("Error fetching user : %s", username));
}
return Response.ok(result.get()).build();
}
/**
* Extracts the user from the request context in order to determine a user is
* signed in. Throws a FinalForbiddenException if there is no user associated
* with this token.
*
* @return The token user.
*/
private DrupalUserInfo getTokenUser() {
DrupalUserInfo tokenUser = (DrupalUserInfo) request.getAttribute(OAuthFilter.TOKEN_USER);
if (tokenUser == null) {
throw new FinalForbiddenException("No user associated with this token");
}
return tokenUser;
}
}
......@@ -21,14 +21,11 @@ import javax.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.core.helper.DateTimeHelper;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.core.service.CachingService.ParameterizedCacheKey;
import org.eclipsefoundation.foundationdb.client.model.PeopleDocumentData;
import org.eclipsefoundation.openvsx.api.PeopleAPI;
import org.eclipsefoundation.openvsx.api.models.AgreementSigningRequest;
import org.eclipsefoundation.openvsx.api.models.DrupalUserInfo;
import org.eclipsefoundation.openvsx.services.PublisherAgreementService;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -43,9 +40,6 @@ public class DefaultPublisherAgreementService implements PublisherAgreementServi
@RestClient
PeopleAPI peopleAPI;
@Inject
CachingService cache;
@Override
public Optional<PeopleDocumentData> getPublisherAgreementByUsername(String username) {
try {
......@@ -101,10 +95,9 @@ public class DefaultPublisherAgreementService implements PublisherAgreementServi
return false;
}
// Delete and remove from cache entry
// Delete
peopleAPI.deletePeopleDocument(username, openvsxDocId,
DateTimeHelper.toRFC3339(mostRecent.get().getEffectiveDate()));
cache.remove(new ParameterizedCacheKey(PeopleDocumentData.class, username, new MultivaluedMapImpl<>()));
return true;
}
......
/*********************************************************************
* 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.openvsx.resources;
import java.util.Map;
import java.util.Optional;
import org.eclipsefoundation.openvsx.test.helpers.SchemaNamespaceHelper;
import org.eclipsefoundation.testing.helpers.TestCaseHelper;
import org.eclipsefoundation.testing.templates.RestAssuredTemplates;
import org.eclipsefoundation.testing.templates.RestAssuredTemplates.EndpointTestCase;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class ProfileResourceTest {
public static final String BASE_URL = "profile";
public static final Optional<Map<String, Object>> userCreds = Optional
.of(Map.of("Authorization", "Bearer token2"));
public static final Optional<Map<String, Object>> noUserCreds = Optional
.of(Map.of("Authorization", "Bearer token6"));
public static final Optional<Map<String, Object>> invalidCreds = Optional
.of(Map.of("Authorization", "Bearer token1"));
public static final EndpointTestCase GET_CURRENT_SUCCESS = TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, SchemaNamespaceHelper.EF_USER_SCHEMA_PATH)
.setHeaderParams(userCreds).build();
/*
* GET CURRENT USER
*/
@Test
void testGetProfile_success() {
RestAssuredTemplates.testGet(GET_CURRENT_SUCCESS);
}
@Test
void testGetProfile_success_validateResponseFormat() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_CURRENT_SUCCESS);
}
@Test
void testGetProfile_success_validateSchema() {
RestAssuredTemplates.testGet_validateResponseFormat(GET_CURRENT_SUCCESS);
}
@Test
void testGetProfile_failure_anonymousToken() {
RestAssuredTemplates.testGet(TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, null)
.setStatusCode(403)
.setHeaderParams(noUserCreds).build());
}
@Test
void testGetProfile_failure_invalidCreds() {
RestAssuredTemplates.testGet(TestCaseHelper
.prepareTestCase(BASE_URL, new String[] {}, null)
.setStatusCode(403)
.setHeaderParams(invalidCreds).build());
}
}
......@@ -69,6 +69,12 @@ public class MockDrupalOAuthAPI implements DrupalOAuthAPI {
.setUserId("333")
.setExpires(Instant.now().getEpochSecond() + 20000)
.setScope("read write admin")
.build(),
DrupalOAuthData.builder()
.setAccessToken("token6")
.setClientId("test-id")
.setExpires(Instant.now().getEpochSecond() + 20000)
.setScope("read write admin")
.build()));
users = new ArrayList<>();
......@@ -98,5 +104,5 @@ public class MockDrupalOAuthAPI implements DrupalOAuthAPI {
}
return users.stream().filter(u -> u.getSub().equalsIgnoreCase(tokenInfo.getUserId())).findFirst().orElse(null);
}
}
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ package org.eclipsefoundation.openvsx.test.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
......@@ -20,6 +21,7 @@ import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.eclipsefoundation.openvsx.api.EclipseAPI;
import org.eclipsefoundation.openvsx.api.models.EfUser;
import org.eclipsefoundation.openvsx.api.models.EfUser.Country;
import io.quarkus.test.Mock;
......@@ -37,26 +39,70 @@ public class MockEclipseAPI implements EclipseAPI {
.setUid(666)
.setName("firstlast")
.setGithubHandle("handle")
.setPicture("pic url")
.setFirstName("fake")
.setLastName("user")
.setPublisherAgreements(new HashMap<>())
.setTwitterHandle("")
.setOrg("null")
.setJobTitle("employee")
.setWebsite("site url")
.setCountry(Country.builder().build())
.setInterests(Arrays.asList())
.setWorkingGroupsInterests(Arrays.asList())
.build(),
EfUser.builder()
.setUid(42)
.setName("fakeuser")
.setPicture("pic url")
.setFirstName("fake")
.setLastName("user")
.setPublisherAgreements(new HashMap<>())
.setGithubHandle("fakeuser")
.setTwitterHandle("")
.setOrg("null")
.setJobTitle("employee")
.setWebsite("site url")
.setCountry(Country.builder().build())
.setInterests(Arrays.asList())
.setWorkingGroupsInterests(Arrays.asList())
.build(),
EfUser.builder()
.setUid(333)
.setName("name")
.setGithubHandle("name")
.setPicture("pic url")
.setFirstName("fake")
.setLastName("user")
.setPublisherAgreements(new HashMap<>())
.setTwitterHandle("")
.setOrg("null")
.setJobTitle("employee")
.setWebsite("site url")
.setCountry(Country.builder().build())
.setInterests(Arrays.asList())
.setWorkingGroupsInterests(Arrays.asList())
.build(),
EfUser.builder()
.setUid(11)
.setName("testtesterson")
.setGithubHandle("mctesty")
.setPicture("pic url")
.setFirstName("fake")
.setLastName("user")
.setPublisherAgreements(new HashMap<>())
.setTwitterHandle("")
.setOrg("null")
.setJobTitle("employee")
.setWebsite("site url")
.setCountry(Country.builder().build())
.setInterests(Arrays.asList())
.setWorkingGroupsInterests(Arrays.asList())
.build()));
}
@Override
public EfUser getUserByUsername(String username) {
return users.stream().filter(u -> u.getName().equalsIgnoreCase(username)).findFirst().orElse(null);
return users.stream().filter(u -> u.getName().equalsIgnoreCase(username)).findFirst().orElse(null);
}
}
......@@ -19,5 +19,6 @@ public class SchemaNamespaceHelper {
+ BASE_SCHEMA_PATH_SUFFIX;
public static final String PUBLISHER_AGREEMENT_SCHEMA_PATH = BASE_SCHEMA_PATH + "publisher-agreement"
+ BASE_SCHEMA_PATH_SUFFIX;
public static final String EF_USER_SCHEMA_PATH = BASE_SCHEMA_PATH + "ef-user" + BASE_SCHEMA_PATH_SUFFIX;
public static final String ERROR_SCHEMA_PATH = BASE_SCHEMA_PATH + "error" + BASE_SCHEMA_PATH_SUFFIX;
}
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