From f8179cb4c8a0e2727c7acd5b832b9e86c2dba2b8 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Mon, 21 Oct 2024 12:08:57 +0100 Subject: [PATCH 1/3] CouchDB now supports user methods Signed-off-by: Aashir Siddiqui --- .../couchdb/internal/CouchdbAuthStore.java | 219 ++++++++++++-- .../internal/CouchdbAuthStoreValidator.java | 42 +-- .../internal/beans/AuthDBNameViewDesign.java | 26 ++ .../beans/TokensDBNameViewDesign.java | 24 -- .../auth/couchdb/TestCouchdbAuthStore.java | 280 ++++++++++++++++-- .../TestCouchdbAuthStoreValidator.java | 34 ++- .../dev/galasa/extensions/common/Errors.java | 5 + .../common/couchdb/CouchdbStore.java | 22 +- .../mocks/MockFrameworkInitialisation.java | 1 + 9 files changed, 548 insertions(+), 105 deletions(-) create mode 100644 galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java delete mode 100644 galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokensDBNameViewDesign.java diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java index a88b3810..3704269c 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java @@ -9,50 +9,68 @@ import java.io.IOException; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.apache.commons.logging.Log; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import dev.galasa.auth.couchdb.internal.beans.AuthDBNameViewDesign; import dev.galasa.extensions.common.api.HttpClientFactory; import dev.galasa.extensions.common.api.LogFactory; import dev.galasa.extensions.common.couchdb.CouchdbException; import dev.galasa.extensions.common.couchdb.CouchdbStore; import dev.galasa.extensions.common.couchdb.CouchdbValidator; +import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewRow; import dev.galasa.extensions.common.api.HttpRequestFactory; import dev.galasa.framework.spi.auth.IInternalAuthToken; import dev.galasa.framework.spi.auth.IInternalUser; +import dev.galasa.framework.spi.auth.UserDoc; import dev.galasa.framework.spi.auth.IAuthStore; import dev.galasa.framework.spi.utils.ITimeService; import dev.galasa.framework.spi.auth.AuthStoreException; +import dev.galasa.framework.spi.auth.FrontendClient; /** - * When CouchDB is being used to store user-related information, including information - * about authentication tokens (but not the tokens themselves), this class is called + * When CouchDB is being used to store user-related information, including + * information + * about authentication tokens (but not the tokens themselves), this class is + * called * upon to implement the auth store. * - * This class registers the auth store as the only auth store in the framework, and is - * only used when Galasa is running in an ecosystem. It gets all of its data from a + * This class registers the auth store as the only auth store in the framework, + * and is + * only used when Galasa is running in an ecosystem. It gets all of its data + * from a * CouchDB server. */ public class CouchdbAuthStore extends CouchdbStore implements IAuthStore { public static final String TOKENS_DATABASE_NAME = "galasa_tokens"; + public static final String USERS_DATABASE_NAME = "galasa_users"; public static final String COUCHDB_AUTH_ENV_VAR = "GALASA_AUTHSTORE_TOKEN"; - public static final String COUCHDB_AUTH_TYPE = "Basic"; + public static final String COUCHDB_AUTH_TYPE = "Basic"; + + public static final String TOKENS_DB_VIEW_NAME = "loginId-view"; + public static final String USERS_DB_VIEW_NAME = "users-loginId-view"; private Log logger; private ITimeService timeService; public CouchdbAuthStore( - URI authStoreUri, - HttpClientFactory httpClientFactory, - HttpRequestFactory requestFactory, - LogFactory logFactory, - CouchdbValidator validator, - ITimeService timeService - ) throws CouchdbException { + URI authStoreUri, + HttpClientFactory httpClientFactory, + HttpRequestFactory requestFactory, + LogFactory logFactory, + CouchdbValidator validator, + ITimeService timeService) throws CouchdbException { super(authStoreUri, requestFactory, httpClientFactory); this.logger = logFactory.getLog(getClass()); this.timeService = timeService; @@ -83,14 +101,14 @@ public List getTokens() throws AuthStoreException { return tokens; } - public List getTokensByLoginId(String loginId) throws AuthStoreException { - logger.info("Retrieving tokens from CouchDB"); + public List getTokensByLoginId(String loginId) throws AuthStoreException { + logger.info("Retrieving tokens from CouchDB"); List tokenDocuments = new ArrayList<>(); List tokens = new ArrayList<>(); try { // Get all of the documents in the tokens database - tokenDocuments = getAllDocsByLoginId(TOKENS_DATABASE_NAME, loginId); + tokenDocuments = getAllDocsByLoginId(TOKENS_DATABASE_NAME, loginId, TOKENS_DB_VIEW_NAME); // Build up a list of all the tokens using the document IDs for (ViewRow row : tokenDocuments) { @@ -103,7 +121,7 @@ public List getTokensByLoginId(String loginId) throws AuthSt throw new AuthStoreException(errorMessage, e); } return tokens; - } + } @Override public void shutdown() throws AuthStoreException { @@ -141,13 +159,176 @@ public void deleteToken(String tokenId) throws AuthStoreException { /** * Gets an auth token from a CouchDB document with the given document ID. - * The document is assumed to be within the tokens database in the CouchDB server. + * The document is assumed to be within the tokens database in the CouchDB + * server. * - * @param documentId the ID of the document containing the details of an auth token + * @param documentId the ID of the document containing the details of an auth + * token * @return the auth token stored within the given document - * @throws AuthStoreException if there was a problem accessing the auth store or its response + * @throws AuthStoreException if there was a problem accessing the auth store or + * its response */ private IInternalAuthToken getAuthTokenFromDocument(String documentId) throws CouchdbException { return getDocumentFromDatabase(TOKENS_DATABASE_NAME, documentId, CouchdbAuthToken.class); } + + @Override + public List getAllUsers() throws AuthStoreException { + logger.info("Retrieving all users from couchdb"); + + List userDocuments = new ArrayList<>(); + List users = new ArrayList<>(); + + try { + userDocuments = getAllDocsFromDatabase(USERS_DATABASE_NAME); + + for (ViewRow row : userDocuments) { + users.add(getUserFromDocument(row.id)); + } + + logger.info("Users retrieved from CouchDB OK"); + + } catch (CouchdbException e) { + String errorMessage = ERROR_FAILED_TO_RETRIEVE_USERS.getMessage(e.getMessage()); + throw new AuthStoreException(errorMessage, e); + } + + return users; + } + + @Override + public void createUser(String loginId, String clientName) throws AuthStoreException { + String userJson = gson.toJson(new UserDoc(loginId, List.of(new FrontendClient(clientName, Instant.now())))); + + try { + createDocument(USERS_DATABASE_NAME, userJson); + } catch (CouchdbException e) { + String errorMessage = ERROR_FAILED_TO_CREATE_USER_DOCUMENT.getMessage(e.getMessage()); + throw new AuthStoreException(errorMessage, e); + } + } + + @Override + public UserDoc getUserByLoginId(String loginId) throws AuthStoreException { + logger.info("Retrieving user by loginId from CouchDB"); + List userDocument = new ArrayList<>(); + List users = new ArrayList<>(); + + UserDoc user = null; + String revNumber = null; + + try { + + userDocument = getAllDocsByLoginId(USERS_DATABASE_NAME, loginId, USERS_DB_VIEW_NAME); + + // Build up a list of all the tokens using the document IDs + for (ViewRow row : userDocument) { + users.add(getUserFromDocument(row.id)); + + if (row.value != null) { + AuthDBNameViewDesign nameViewDesign = gson.fromJson(gson.toJson(row.value), + AuthDBNameViewDesign.class); + revNumber = nameViewDesign._rev; + } + + } + logger.info("User retrieved from CouchDB OK"); + + } catch (CouchdbException e) { + String errorMessage = ERROR_FAILED_TO_RETRIEVE_USERS.getMessage(e.getMessage()); + throw new AuthStoreException(errorMessage, e); + } + + // Always going to return one entry, as loginIds are unique. + // Hence, we can take out the first index + if (!users.isEmpty()) { + + user = users.get(0); + user.setUserNumber(userDocument.get(0).id); + user.setVersion(revNumber); + + } + + return user; + + } + + @Override + public void updateUserClientActivity(String loginId, String clientName) throws AuthStoreException { + + UserDoc user = getUserByLoginId(loginId); + + List clients = user.getClients(); + + Optional clientOptional = clients.stream() + .filter(client -> client.getClientName().equals(clientName)) + .findFirst(); + + if (clientOptional.isPresent()) { + FrontendClient client = clientOptional.get(); + client.setLastLoggedIn(Instant.now()); + } else { + // User has used a new client. + FrontendClient newClient = new FrontendClient(clientName, Instant.now()); + clients.add(newClient); + } + + try { + updateUserDoc(httpClient, storeUri, 0, user); + } catch (CouchdbException e) { + e.printStackTrace(); + throw new AuthStoreException(e); + } + } + + private void updateUserDoc(CloseableHttpClient httpClient, URI couchdbUri, int attempts, UserDoc user) + throws CouchdbException { + + String jsonStructure = gson.toJson(user); + + HttpEntityEnclosingRequestBase request; + + if (user.getUserNumber() == null) { + request = httpRequestFactory.getHttpPostRequest(couchdbUri + "/" + USERS_DATABASE_NAME); + } else { + request = httpRequestFactory + .getHttpPutRequest(couchdbUri + "/" + USERS_DATABASE_NAME + "/" + user.getUserNumber()); + request.setHeader("If-Match", user.getVersion()); + + logger.info("Rev is: " + user.getVersion()); + } + + request.setEntity(new StringEntity(jsonStructure, StandardCharsets.UTF_8)); + + try { + String entity = sendHttpRequest(request, HttpStatus.SC_CREATED); + PutPostResponse putPostResponse = gson.fromJson(entity, PutPostResponse.class); + + if (putPostResponse.id == null || putPostResponse.rev == null) { + throw new CouchdbException("Unable to store the user structure - Invalid JSON response"); + } + + user.setUserNumber(putPostResponse.id); + user.setVersion(putPostResponse.rev); + + } catch (CouchdbException e) { + throw new CouchdbException(e); + } + + } + + /** + * Gets a user from a CouchDB document with the given document ID. + * The document is assumed to be within the users database in the CouchDB + * server. + * + * @param documentId the ID of the document containing the details of a user + * @return the user stored within the given document + * @throws UsersStoreException if there was a problem accessing the users store + * or its response + */ + private UserDoc getUserFromDocument(String documentId) throws CouchdbException { + return getDocumentFromDatabase(USERS_DATABASE_NAME, documentId, UserDoc.class); + } + } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java index 3c3e8d41..a9378019 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java @@ -43,6 +43,7 @@ public class CouchdbAuthStoreValidator extends CouchdbBaseValidator { // A couchDB view, it gets all the access tokens of a the user based on the loginId provided. public static final String DB_TABLE_TOKENS_DESIGN = "function (doc) { if (doc.owner && doc.owner.loginId) {emit(doc.owner.loginId, doc); } }"; + public static final String DB_TABLE_USERS_DESIGN = "function (doc) { if (doc['login-id']) { emit(doc['login-id'], doc); } }"; public CouchdbAuthStoreValidator() { this(new LogFactory(){ @@ -72,7 +73,8 @@ public void checkCouchdbDatabaseIsValid( RetryableCouchdbUpdateOperationProcessor retryProcessor = new RetryableCouchdbUpdateOperationProcessor(timeService, this.logFactory); retryProcessor.retryCouchDbUpdateOperation( - ()->{ tryToCheckAndUpdateCouchDBTokenView(couchdbUri, httpClient, httpRequestFactory); + ()->{ + tryToCheckAndUpdateCouchDBTokenView(couchdbUri, httpClient, httpRequestFactory); }); logger.debug("Auth Store CouchDB at " + couchdbUri.toString() + " validated"); @@ -82,39 +84,41 @@ private void tryToCheckAndUpdateCouchDBTokenView(URI couchdbUri, CloseableHttpCl HttpRequestFactory httpRequestFactory) throws CouchdbException { validateDatabasePresent(couchdbUri, CouchdbAuthStore.TOKENS_DATABASE_NAME); - checkTokensDesignDocument(httpClient, couchdbUri, 1); + validateDatabasePresent(couchdbUri, CouchdbAuthStore.USERS_DATABASE_NAME); + checkTokensDesignDocument(httpClient, couchdbUri, 1, CouchdbAuthStore.TOKENS_DATABASE_NAME); + checkTokensDesignDocument(httpClient, couchdbUri, 1, CouchdbAuthStore.USERS_DATABASE_NAME); } - public void checkTokensDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts) + public void checkTokensDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts, String dbName) throws CouchdbException { // Get the design document from couchdb - String docJson = getTokenDesignDocument(httpClient, couchdbUri, attempts); + String docJson = getDatabaseDesignDocument(httpClient, couchdbUri, attempts, dbName); - TokensDBNameViewDesign tableDesign = parseTokenDesignFromJson(docJson); + AuthDBNameViewDesign tableDesign = parseTokenDesignFromJson(docJson); boolean isDesignUpdated = updateDesignDocToDesiredDesignDoc(tableDesign); if (isDesignUpdated) { - updateTokenDesignDocument(httpClient, couchdbUri, attempts, tableDesign); + updateTokenDesignDocument(httpClient, couchdbUri, attempts, tableDesign, dbName); } } - private TokensDBNameViewDesign parseTokenDesignFromJson(String docJson) throws CouchdbException { - TokensDBNameViewDesign tableDesign; + private AuthDBNameViewDesign parseTokenDesignFromJson(String docJson) throws CouchdbException { + AuthDBNameViewDesign tableDesign; try { - tableDesign = gson.fromJson(docJson, TokensDBNameViewDesign.class); + tableDesign = gson.fromJson(docJson, AuthDBNameViewDesign.class); } catch (JsonSyntaxException ex) { throw new CouchdbException(ERROR_FAILED_TO_PARSE_COUCHDB_DESIGN_DOC.getMessage(ex.getMessage()), ex); } if (tableDesign == null) { - tableDesign = new TokensDBNameViewDesign(); + tableDesign = new AuthDBNameViewDesign(); } return tableDesign; } - private boolean updateDesignDocToDesiredDesignDoc(TokensDBNameViewDesign tableDesign) { + private boolean updateDesignDocToDesiredDesignDoc(AuthDBNameViewDesign tableDesign) { boolean isUpdated = false; if (tableDesign.views == null) { @@ -141,10 +145,10 @@ private boolean updateDesignDocToDesiredDesignDoc(TokensDBNameViewDesign tableDe return isUpdated; } - private String getTokenDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts) + private String getDatabaseDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts, String dbName) throws CouchdbException { HttpRequestFactory requestFactory = super.getRequestFactory(); - HttpGet httpGet = requestFactory.getHttpGetRequest(couchdbUri + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME +"/_design/docs"); + HttpGet httpGet = requestFactory.getHttpGetRequest(couchdbUri + "/" + dbName +"/_design/docs"); String docJson = null; try (CloseableHttpResponse response = httpClient.execute(httpGet)) { @@ -155,7 +159,7 @@ private String getTokenDesignDocument(CloseableHttpClient httpClient, URI couchd if (statusLine.getStatusCode() != HttpStatus.SC_OK && statusLine.getStatusCode() != HttpStatus.SC_NOT_FOUND) { throw new CouchdbException( - "Validation failed of database galasa_tokens design document - " + statusLine.toString()); + "Validation failed of database " + dbName + " design document - " + statusLine.toString()); } if (statusLine.getStatusCode() == HttpStatus.SC_NOT_FOUND) { docJson = "{}"; @@ -171,14 +175,14 @@ private String getTokenDesignDocument(CloseableHttpClient httpClient, URI couchd } private void updateTokenDesignDocument(CloseableHttpClient httpClient, URI couchdbUri, int attempts, - TokensDBNameViewDesign tokenViewDesign) throws CouchdbException { + AuthDBNameViewDesign tokenViewDesign, String dbName) throws CouchdbException { HttpRequestFactory requestFactory = super.getRequestFactory(); - logger.info("Updating the galasa_tokens design document"); + logger.info("Updating the " + dbName + " design document"); HttpEntity entity = new StringEntity(gson.toJson(tokenViewDesign), ContentType.APPLICATION_JSON); - HttpPut httpPut = requestFactory.getHttpPutRequest(couchdbUri + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME +"/_design/docs"); + HttpPut httpPut = requestFactory.getHttpPutRequest(couchdbUri + "/" + dbName +"/_design/docs"); httpPut.setEntity(entity); if (tokenViewDesign._rev != null) { @@ -200,13 +204,13 @@ private void updateTokenDesignDocument(CloseableHttpClient httpClient, URI couch if (statusCode != HttpStatus.SC_CREATED) { throw new CouchdbException( - "Update of galasa_tokens design document failed on CouchDB server - " + statusLine.toString()); + "Update of " + dbName + " design document failed on CouchDB server - " + statusLine.toString()); } } catch (CouchdbException e) { throw e; } catch (Exception e) { - throw new CouchdbException("Update of galasa_tokens design document failed", e); + throw new CouchdbException("Update of " +dbName + " design document failed", e); } } } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java new file mode 100644 index 00000000..8a508382 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java @@ -0,0 +1,26 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.auth.couchdb.internal.beans; + + +public class AuthDBNameViewDesign { + public String _rev; + public String _id; + public TokenDBViews views; + public String language; + + public AuthDBNameViewDesign(String _rev, String _id, TokenDBViews views, String language) { + this._rev = _rev; + this._id = _id; + this.views = views; + this.language = language; + } + + public AuthDBNameViewDesign(){ + + } +} + \ No newline at end of file diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokensDBNameViewDesign.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokensDBNameViewDesign.java deleted file mode 100644 index 8c7add64..00000000 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokensDBNameViewDesign.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright contributors to the Galasa project - * - * SPDX-License-Identifier: EPL-2.0 - */ -package dev.galasa.auth.couchdb.internal.beans; - -//{ -// "_id": "_design/docs", -// "_rev": "3-xxxxxxxxxxx9c9072dyy", -// "views": { -// "loginId-view": { -// "map": "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}" -// } -// }, -// "language": "javascript" -// } -public class TokensDBNameViewDesign { - public String _rev; - public String _id; - public TokenDBViews views; - public String language; -} - \ No newline at end of file diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java index 975c65d8..4faf6e28 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java @@ -20,6 +20,8 @@ import dev.galasa.auth.couchdb.internal.CouchdbAuthStore; import dev.galasa.auth.couchdb.internal.CouchdbAuthToken; import dev.galasa.auth.couchdb.internal.CouchdbUser; +import dev.galasa.auth.couchdb.internal.beans.TokenDBViews; +import dev.galasa.auth.couchdb.internal.beans.AuthDBNameViewDesign; import dev.galasa.extensions.common.couchdb.pojos.IdRev; import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewResponse; @@ -33,13 +35,15 @@ import dev.galasa.extensions.mocks.MockTimeService; import dev.galasa.extensions.mocks.couchdb.MockCouchdbValidator; import dev.galasa.framework.spi.auth.AuthStoreException; +import dev.galasa.framework.spi.auth.FrontendClient; import dev.galasa.framework.spi.auth.IInternalAuthToken; +import dev.galasa.framework.spi.auth.UserDoc; public class TestCouchdbAuthStore { - class GetAllTokenDocumentsInteraction extends BaseHttpInteraction { + class GetAllDocumentsInteraction extends BaseHttpInteraction { - public GetAllTokenDocumentsInteraction(String expectedUri, int responseStatusCode, ViewResponse tokenDocsToReturn) { + public GetAllDocumentsInteraction(String expectedUri, int responseStatusCode, ViewResponse tokenDocsToReturn) { super(expectedUri, responseStatusCode); setResponsePayload(tokenDocsToReturn); } @@ -51,9 +55,9 @@ public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeEx } } - class GetTokenDocumentInteraction extends BaseHttpInteraction { + class GetDocumentInteraction extends BaseHttpInteraction { - public GetTokenDocumentInteraction(String expectedUri, int responseStatusCode, T responseObjToReturn) { + public GetDocumentInteraction(String expectedUri, int responseStatusCode, T responseObjToReturn) { super(expectedUri, responseStatusCode); setResponsePayload(responseObjToReturn); } @@ -79,9 +83,9 @@ public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeEx } } - class CreateTokenDocInteraction extends BaseHttpInteraction { + class CreateDocumentInteraction extends BaseHttpInteraction { - public CreateTokenDocInteraction(String expectedUri, int responseStatusCode) { + public CreateDocumentInteraction(String expectedUri, int responseStatusCode) { super(expectedUri, responseStatusCode); PutPostResponse responseTransportBean = new PutPostResponse(); @@ -98,14 +102,33 @@ public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeEx } } + class UpdateDocumentInteraction extends BaseHttpInteraction { + + public UpdateDocumentInteraction(String expectedUri, int responseStatusCode) { + super(expectedUri, responseStatusCode); + + PutPostResponse responseTransportBean = new PutPostResponse(); + responseTransportBean.id = "id"; + responseTransportBean.ok = true; + responseTransportBean.rev = "rev"; + setResponsePayload(responseTransportBean); + } + + @Override + public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { + super.validateRequest(host,request); + assertThat(request.getRequestLine().getMethod()).isEqualTo("PUT"); + } + } + @Test - public void testGetTokensReturnsTokensWithFailingRequestReturnsError() throws Exception { + public void testGetTokensReturnsTokensWithFailingRequestReturnsErrorDup() throws Exception { // Given... URI authStoreUri = URI.create("couchdb:https://my-auth-store"); MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new GetAllTokenDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -137,8 +160,8 @@ public void testGetTokensReturnsTokensFromCouchdbOK() throws Exception { CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), new CouchdbUser("johndoe", "dex-user-id")); List interactions = new ArrayList(); - interactions.add(new GetAllTokenDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetTokenDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -173,9 +196,9 @@ public void testGetTokensReturnsTokensByLoginIdFromCouchdbOK() throws Exception CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), new CouchdbUser("johndoe", "dex-user-id")); CouchdbAuthToken mockToken2 = new CouchdbAuthToken("token2", "dex-client", "my test token", Instant.now(), new CouchdbUser("notJohnDoe", "dex-user-id")); List interactions = new ArrayList(); - interactions.add(new GetAllTokenDocumentsInteraction("https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetTokenDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); - interactions.add(new GetTokenDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken2)); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken2)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -200,7 +223,7 @@ public void testGetTokensReturnsTokensByLoginIdWithFailingRequestReturnsError() MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new GetAllTokenDocumentsInteraction("https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -224,7 +247,7 @@ public void testStoreTokenSendsRequestToCreateTokenDocumentOK() throws Exception MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new CreateTokenDocInteraction("https://my-auth-store/galasa_tokens", HttpStatus.SC_CREATED)); + interactions.add(new CreateDocumentInteraction("https://my-auth-store/galasa_tokens", HttpStatus.SC_CREATED)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -246,7 +269,7 @@ public void testStoreTokenWithFailingRequestToCreateTokenDocumentReturnsError() MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new CreateTokenDocInteraction("https://my-auth-store/galasa_tokens", HttpStatus.SC_INTERNAL_SERVER_ERROR)); + interactions.add(new CreateDocumentInteraction("https://my-auth-store/galasa_tokens", HttpStatus.SC_INTERNAL_SERVER_ERROR)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -278,7 +301,7 @@ public void testDeleteTokenSendsRequestToDeleteTokenDocumentOK() throws Exceptio String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; List interactions = new ArrayList(); - interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); + interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_OK)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -309,7 +332,7 @@ public void testDeleteTokenWithAcceptedRequestToDeleteTokenDocumentDoesNotError( String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; List interactions = new ArrayList(); - interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); + interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); // The DELETE request may return a 202 Accepted, which shouldn't be a problem for us interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_ACCEPTED)); @@ -342,7 +365,7 @@ public void testDeleteTokenWithFailingRequestToDeleteTokenDocumentThrowsError() String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; List interactions = new ArrayList(); - interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); + interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); // Simulate an internal server error interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR)); @@ -381,7 +404,7 @@ public void testDeleteTokenWithFailingRequestToGetTokenDocumentThrowsError() thr List interactions = new ArrayList(); // Simulate an internal server error - interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR, mockIdRev)); + interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR, mockIdRev)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -413,7 +436,7 @@ public void testDeleteTokenWithBadGetTokenDocumentResponseBodyThrowsError() thro List interactions = new ArrayList(); // Simulate an internal server error - interactions.add(new GetTokenDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, null)); + interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, null)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -432,4 +455,219 @@ public void testDeleteTokenWithBadGetTokenDocumentResponseBodyThrowsError() thro assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); assertThat(thrown.getMessage()).contains("GAL6011E", "Failed to get document with ID 'my-old-token' from the 'galasa_tokens' database"); } + + @Test + public void testGetAllUsersReturnsListOfUsersFroasmCouchdbOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-users-store"); + MockLogFactory logFactory = new MockLogFactory(); + + ViewRow userDoc = new ViewRow(); + ViewRow userDoc2 = new ViewRow(); + + userDoc.id = "user1"; + userDoc2.id = "user2"; + + List mockDocs = List.of(userDoc,userDoc2); + + ViewResponse mockAllDocsResponse = new ViewResponse(); + mockAllDocsResponse.rows = mockDocs; + + UserDoc mockUser = new UserDoc("user1", List.of()); + UserDoc mockUser2 = new UserDoc("user2", List.of()); + List interactions = new ArrayList(); + interactions.add(new GetAllDocumentsInteraction("https://my-users-store/galasa_users/_all_docs", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-users-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + interactions.add(new GetDocumentInteraction("https://my-users-store/galasa_users/user2", HttpStatus.SC_OK, mockUser2)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + // When... + List users = authStore.getAllUsers(); + + // Then... + assertThat(users).hasSize(2); + } + + @Test + public void testGetTokensReturnsTokensWithFailingRequestReturnsError() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-users-store"); + MockLogFactory logFactory = new MockLogFactory(); + + List interactions = new ArrayList(); + interactions.add(new GetAllDocumentsInteraction("https://my-users-store/galasa_users/_all_docs", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + AuthStoreException thrown = catchThrowableOfType(() -> authStore.getAllUsers(), AuthStoreException.class); + + // Then... + assertThat(thrown).isNotNull(); + assertThat(thrown.getMessage()).contains("GAL6202E", "Failed to get user documents from the CouchDB users store."); + } + + @Test + public void testStoreTokenSendsRequestToasCreateTokenDocumentOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-users-store"); + MockLogFactory logFactory = new MockLogFactory(); + + List interactions = new ArrayList(); + interactions.add(new CreateDocumentInteraction("https://my-users-store/galasa_users", HttpStatus.SC_CREATED)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + // When... + authStore.createUser("this-is-a-login-id", "rest-api"); + + // Then the assertions made in the create users document interaction shouldn't have failed. + } + + @Test + public void testGetUserReturnsUsersByLoginIdFromCouchdbOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + ViewRow userDoc1 = new ViewRow(); + + userDoc1.id = "user1"; + List mockDocs = List.of(userDoc1); + + ViewResponse mockAllDocsResponse = new ViewResponse(); + mockAllDocsResponse.rows = mockDocs; + + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); + + List interactions = new ArrayList(); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + // When... + UserDoc user = authStore.getUserByLoginId("johndoe"); + + assertThat(user).isNotNull(); + } + + @Test + public void testGetUserReturnsCorrectUserByLoginIdFromCouchdbOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + ViewRow userDoc1 = new ViewRow(); + + userDoc1.id = "user1"; + List mockDocs = List.of(userDoc1); + + ViewResponse mockAllDocsResponse = new ViewResponse(); + mockAllDocsResponse.rows = mockDocs; + + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); + + List interactions = new ArrayList(); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22notJohndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + // When... + UserDoc user = authStore.getUserByLoginId("notJohndoe"); + + assertThat(user).usingRecursiveComparison().isNotEqualTo(mockUser); + } + + @Test + public void testUpdateUserUpdatesExisitingClientOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + ViewRow userDoc1 = new ViewRow(); + + userDoc1.id = "user1"; + List mockDocs = List.of(userDoc1); + + ViewResponse mockAllDocsResponse = new ViewResponse(); + mockAllDocsResponse.rows = mockDocs; + + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); + + List interactions = new ArrayList(); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + interactions.add(new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + authStore.updateUserClientActivity("johndoe", "web-ui"); + + // Then the assertions made in the updates users document interaction shouldn't have failed. + } + + @Test + public void testUpdateUserAddsNewClientToUserOK() throws Exception { + // Given... + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + ViewRow userDoc1 = new ViewRow(); + + userDoc1.id = "user1"; + userDoc1.key = "admin"; + userDoc1.value = new AuthDBNameViewDesign("hello", "world", new TokenDBViews(), "javascript"); + List mockDocs = List.of(userDoc1); + + ViewResponse mockAllDocsResponse = new ViewResponse(); + mockAllDocsResponse.rows = mockDocs; + + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); + + List interactions = new ArrayList(); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + interactions.add(new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED)); + + MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + + authStore.updateUserClientActivity("johndoe", "rest-api"); + + // Then the assertions made in the updates users document interaction shouldn't have failed. + } } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java index f183a819..4092c62a 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java @@ -122,6 +122,7 @@ public void testCheckCouchdbDatabaseIsValidWithValidDatabaseIsOK() throws Except List interactions = new ArrayList<>(); interactions.add(new GetCouchdbWelcomeInteraction(couchdbUriStr, welcomeMessage)); interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME, HttpStatus.SC_OK)); + interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + CouchdbAuthStore.USERS_DATABASE_NAME, HttpStatus.SC_OK)); // We are expecting this to ne returned from our mock couchdb server to the code: @@ -138,16 +139,17 @@ public void testCheckCouchdbDatabaseIsValidWithValidDatabaseIsOK() throws Except view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; TokenDBViews views = new TokenDBViews(); views.loginIdView = view; - TokensDBNameViewDesign designDocToPassBack = new TokensDBNameViewDesign(); + AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; - - String tokensDesignDocUrl = couchdbUriStr + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME + "/_design/docs"; interactions.add(new GetTokensDatabaseDesignInteraction(tokensDesignDocUrl, designDocToPassBack)); - - interactions.add(new UpdateTokensDatabaseDesignInteraction(tokensDesignDocUrl, "", HttpStatus.SC_CREATED)); + + String usersDesignDocUrl = couchdbUriStr + "/" + CouchdbAuthStore.USERS_DATABASE_NAME + "/_design/docs"; + interactions.add(new GetTokensDatabaseDesignInteraction(usersDesignDocUrl, designDocToPassBack)); + interactions.add(new UpdateTokensDatabaseDesignInteraction(usersDesignDocUrl, "", HttpStatus.SC_CREATED)); + CloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockTimeService mockTimeService = new MockTimeService(Instant.now()); @@ -197,25 +199,33 @@ public void testCheckCouchdbDatabaseIsValidWithSuccessfulDatabaseCreationIsOK() welcomeMessage.version = CouchDbVersion.COUCHDB_MIN_VERSION.toString(); String tokensDatabaseName = CouchdbAuthStore.TOKENS_DATABASE_NAME; + String usersDatabaseName = CouchdbAuthStore.USERS_DATABASE_NAME; + List interactions = new ArrayList<>(); interactions.add(new GetCouchdbWelcomeInteraction(couchdbUriStr, welcomeMessage)); interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + tokensDatabaseName, HttpStatus.SC_NOT_FOUND)); interactions.add(new CreateDatabaseInteraction(couchdbUriStr + "/" + tokensDatabaseName, HttpStatus.SC_CREATED)); + interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + usersDatabaseName, HttpStatus.SC_NOT_FOUND)); + interactions.add(new CreateDatabaseInteraction(couchdbUriStr + "/" + usersDatabaseName, HttpStatus.SC_CREATED)); TokenDBLoginView view = new TokenDBLoginView(); view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; TokenDBViews views = new TokenDBViews(); views.loginIdView = view; - TokensDBNameViewDesign designDocToPassBack = new TokensDBNameViewDesign(); + AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; String tokensDesignDocUrl = couchdbUriStr + "/" + tokensDatabaseName + "/_design/docs"; + String userDesignDocUrl = couchdbUriStr + "/" + usersDatabaseName + "/_design/docs"; + interactions.add(new GetTokensDatabaseDesignInteraction(tokensDesignDocUrl, designDocToPassBack)); + interactions.add(new UpdateTokensDatabaseDesignInteraction(tokensDesignDocUrl, "",HttpStatus.SC_CREATED)); - MockTimeService mockTimeService = new MockTimeService(Instant.now()); + interactions.add(new GetTokensDatabaseDesignInteraction(userDesignDocUrl, designDocToPassBack)); + interactions.add(new UpdateTokensDatabaseDesignInteraction(userDesignDocUrl, "",HttpStatus.SC_CREATED)); - interactions.add(new UpdateTokensDatabaseDesignInteraction(tokensDesignDocUrl, "",HttpStatus.SC_CREATED)); + MockTimeService mockTimeService = new MockTimeService(Instant.now()); CloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); // When... @@ -424,7 +434,7 @@ public void testCheckCouchdbDatabaseIsValidWithFailedDesignDocResponseThrowsErro List interactions = new ArrayList<>(); interactions.add(new GetCouchdbWelcomeInteraction(couchdbUriStr, welcomeMessage)); interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME, HttpStatus.SC_OK)); - + interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + CouchdbAuthStore.USERS_DATABASE_NAME, HttpStatus.SC_OK)); // We are expecting this to ne returned from our mock couchdb server to the code: // "_id": "_design/docs", @@ -440,7 +450,7 @@ public void testCheckCouchdbDatabaseIsValidWithFailedDesignDocResponseThrowsErro view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; TokenDBViews views = new TokenDBViews(); views.loginIdView = view; - TokensDBNameViewDesign designDocToPassBack = new TokensDBNameViewDesign(); + AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; @@ -475,7 +485,7 @@ public void testCheckCouchdbDatabaseIsValidWithUpdateDesignDocResponseThrowsErro List interactions = new ArrayList<>(); interactions.add(new GetCouchdbWelcomeInteraction(couchdbUriStr, welcomeMessage)); interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + CouchdbAuthStore.TOKENS_DATABASE_NAME, HttpStatus.SC_OK)); - + interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + CouchdbAuthStore.USERS_DATABASE_NAME, HttpStatus.SC_OK)); // We are expecting this to ne returned from our mock couchdb server to the code: // "_id": "_design/docs", @@ -491,7 +501,7 @@ public void testCheckCouchdbDatabaseIsValidWithUpdateDesignDocResponseThrowsErro view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; TokenDBViews views = new TokenDBViews(); views.loginIdView = view; - TokensDBNameViewDesign designDocToPassBack = new TokensDBNameViewDesign(); + AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java index 7b0df5f4..eb6cabcb 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java @@ -35,6 +35,11 @@ public enum Errors { ERROR_FAILED_TO_INITIALISE_AUTH_STORE (6103,"GAL6103E: Failed to initialise the Galasa CouchDB auth store. Cause: {0}"), ERROR_FAILED_TO_DELETE_TOKEN_DOCUMENT (6104,"GAL6104E: Failed to delete auth token from the CouchDB tokens database. Cause: {0}"), + // CouchDB Users Store error + ERROR_FAILED_TO_INITIALISE_USERS_STORE (6200,"GAL6200E: Failed to initialise the Galasa CouchDB users store. Cause: {0}"), + ERROR_FAILED_TO_CREATE_USER_DOCUMENT (6201,"GAL6201E: Failed to store users token in the CouchDB users database. Cause: {0}"), + ERROR_FAILED_TO_RETRIEVE_USERS (6202,"GAL6202E: Failed to get user documents from the CouchDB users store. Cause: {0}"), + // REST CPS errors ERROR_GALASA_WRONG_NUMBER_OF_PARAMETERS_IN_MESSAGE (6999,"GAL6999E: Failed to render message template. Not the expected number of parameters. Got ''{0}''. Expected ''{1}''"), ERROR_GALASA_CONSTRUCTED_URL_TO_REMOTE_CPS_INVALID_SYNTAX (7002,"GAL7002E: URL ''{0}'' is of an invalid syntax. {1}"), diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java index 5f93876b..17fcc363 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java @@ -112,8 +112,8 @@ protected PutPostResponse createDocument(String dbName, String jsonContent) thro */ protected List getAllDocsFromDatabase(String dbName) throws CouchdbException { - HttpGet getTokensDocs = httpRequestFactory.getHttpGetRequest(storeUri + "/" + dbName + "/_all_docs"); - String responseEntity = sendHttpRequest(getTokensDocs, HttpStatus.SC_OK); + HttpGet fetchedDocs = httpRequestFactory.getHttpGetRequest(storeUri + "/" + dbName + "/_all_docs"); + String responseEntity = sendHttpRequest(fetchedDocs, HttpStatus.SC_OK); ViewResponse allDocs = gson.fromJson(responseEntity, ViewResponse.class); List viewRows = allDocs.rows; @@ -124,9 +124,11 @@ protected List getAllDocsFromDatabase(String dbName) throws CouchdbExce } // Filter out design documents from the results - viewRows = viewRows.stream() - .filter((row) -> !row.key.equals("_design/docs")) - .collect(Collectors.toList()); + if(viewRows.get(0).key != null){ + viewRows = viewRows.stream() + .filter((row) -> !row.key.equals("_design/docs")) + .collect(Collectors.toList()); + } return viewRows; } @@ -143,15 +145,15 @@ protected List getAllDocsFromDatabase(String dbName) throws CouchdbExce * @throws CouchdbException if there was a problem accessing the * CouchDB store or its response */ - protected List getAllDocsByLoginId(String dbName, String loginId) throws CouchdbException { + protected List getAllDocsByLoginId(String dbName, String loginId, String viewName) throws CouchdbException { String encodedLoginId = URLEncoder.encode("\"" + loginId + "\"", StandardCharsets.UTF_8); - String url = storeUri + "/" + dbName + "/_design/docs/_view/loginId-view?key=" + encodedLoginId; + String url = storeUri + "/" + dbName + "/_design/docs/_view/" + viewName + "?key=" + encodedLoginId; - HttpGet getTokensDocs = httpRequestFactory.getHttpGetRequest(url); - getTokensDocs.addHeader("Content-Type", "application/json"); + HttpGet getDocs = httpRequestFactory.getHttpGetRequest(url); + getDocs.addHeader("Content-Type", "application/json"); - String responseEntity = sendHttpRequest(getTokensDocs, HttpStatus.SC_OK); + String responseEntity = sendHttpRequest(getDocs, HttpStatus.SC_OK); ViewResponse docByLoginId = gson.fromJson(responseEntity, ViewResponse.class); List viewRows = docByLoginId.rows; diff --git a/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java b/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java index bedd2cb5..699f7185 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java +++ b/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java @@ -33,6 +33,7 @@ public class MockFrameworkInitialisation implements IApiServerInitialisation { private MockFramework framework; protected URI authStoreUri; + protected URI usersStoreUri; protected URI cpsBootstrapUri; private List registeredAuthStores = new ArrayList(); From a0678b270cf4718c776c4433bc1df6713c72cdc7 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Thu, 24 Oct 2024 14:53:06 +0100 Subject: [PATCH 2/3] Refactored extensions and fixed tests Signed-off-by: Aashir Siddiqui --- .../couchdb/internal/CouchdbAuthStore.java | 170 ++++---- .../internal/CouchdbAuthStoreValidator.java | 18 +- .../internal/beans/AuthDBNameViewDesign.java | 4 +- ...ginView.java => AuthStoreDBLoginView.java} | 2 +- ...okenDBViews.java => AuthStoreDBViews.java} | 4 +- .../internal/beans/FrontEndClient.java | 55 +++ .../auth/couchdb/internal/beans/UserDoc.java | 114 +++++ .../{ => internal}/TestCouchdbAuthStore.java | 392 ++++++++++++------ .../TestCouchdbAuthStoreRegistration.java | 4 +- .../TestCouchdbAuthStoreValidator.java | 20 +- .../couchdb/internal/beans/TestUserDoc.java | 278 +++++++++++++ .../dev/galasa/extensions/common/Errors.java | 11 +- .../common/couchdb/CouchdbStore.java | 3 + 13 files changed, 849 insertions(+), 226 deletions(-) rename galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/{TokenDBLoginView.java => AuthStoreDBLoginView.java} (82%) rename galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/{TokenDBViews.java => AuthStoreDBViews.java} (75%) create mode 100644 galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java create mode 100644 galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java rename galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/{ => internal}/TestCouchdbAuthStore.java (64%) rename galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/{ => internal}/TestCouchdbAuthStoreRegistration.java (95%) rename galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/{ => internal}/TestCouchdbAuthStoreValidator.java (97%) create mode 100644 galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java index 3704269c..3f31816a 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java @@ -13,7 +13,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import org.apache.commons.logging.Log; import org.apache.http.HttpStatus; @@ -22,6 +21,8 @@ import org.apache.http.impl.client.CloseableHttpClient; import dev.galasa.auth.couchdb.internal.beans.AuthDBNameViewDesign; +import dev.galasa.auth.couchdb.internal.beans.FrontEndClient; +import dev.galasa.auth.couchdb.internal.beans.UserDoc; import dev.galasa.extensions.common.api.HttpClientFactory; import dev.galasa.extensions.common.api.LogFactory; import dev.galasa.extensions.common.couchdb.CouchdbException; @@ -32,11 +33,11 @@ import dev.galasa.extensions.common.api.HttpRequestFactory; import dev.galasa.framework.spi.auth.IInternalAuthToken; import dev.galasa.framework.spi.auth.IInternalUser; -import dev.galasa.framework.spi.auth.UserDoc; +import dev.galasa.framework.spi.auth.IUser; import dev.galasa.framework.spi.auth.IAuthStore; +import dev.galasa.framework.spi.auth.IFrontEndClient; import dev.galasa.framework.spi.utils.ITimeService; import dev.galasa.framework.spi.auth.AuthStoreException; -import dev.galasa.framework.spi.auth.FrontendClient; /** * When CouchDB is being used to store user-related information, including @@ -59,7 +60,7 @@ public class CouchdbAuthStore extends CouchdbStore implements IAuthStore { public static final String COUCHDB_AUTH_TYPE = "Basic"; public static final String TOKENS_DB_VIEW_NAME = "loginId-view"; - public static final String USERS_DB_VIEW_NAME = "users-loginId-view"; + public static final String USERS_DB_VIEW_NAME = "loginId-view"; private Log logger; private ITimeService timeService; @@ -173,11 +174,11 @@ private IInternalAuthToken getAuthTokenFromDocument(String documentId) throws Co } @Override - public List getAllUsers() throws AuthStoreException { + public List getAllUsers() throws AuthStoreException { logger.info("Retrieving all users from couchdb"); List userDocuments = new ArrayList<>(); - List users = new ArrayList<>(); + List users = new ArrayList<>(); try { userDocuments = getAllDocsFromDatabase(USERS_DATABASE_NAME); @@ -198,7 +199,13 @@ public List getAllUsers() throws AuthStoreException { @Override public void createUser(String loginId, String clientName) throws AuthStoreException { - String userJson = gson.toJson(new UserDoc(loginId, List.of(new FrontendClient(clientName, Instant.now())))); + + FrontEndClient client = new FrontEndClient(); + + client.setClientName(clientName); + client.setLastLogin(Instant.now()); + + String userJson = gson.toJson(new UserDoc(loginId, List.of(client))); try { createDocument(USERS_DATABASE_NAME, userJson); @@ -206,32 +213,38 @@ public void createUser(String loginId, String clientName) throws AuthStoreExcept String errorMessage = ERROR_FAILED_TO_CREATE_USER_DOCUMENT.getMessage(e.getMessage()); throw new AuthStoreException(errorMessage, e); } + + return; } @Override - public UserDoc getUserByLoginId(String loginId) throws AuthStoreException { + public IUser getUserByLoginId(String loginId) throws AuthStoreException { logger.info("Retrieving user by loginId from CouchDB"); - List userDocument = new ArrayList<>(); - List users = new ArrayList<>(); - - UserDoc user = null; - String revNumber = null; + List userDocument; + IUser user = null; try { - + // Fetch documents matching the loginId userDocument = getAllDocsByLoginId(USERS_DATABASE_NAME, loginId, USERS_DB_VIEW_NAME); - // Build up a list of all the tokens using the document IDs - for (ViewRow row : userDocument) { - users.add(getUserFromDocument(row.id)); + // Since loginIds are unique, there should be only one document. + if (!userDocument.isEmpty()) { + ViewRow row = userDocument.get(0); // Get the first entry since loginId is unique + + // Fetch the user document from the CouchDB using the ID from the row + UserDoc fetchedUser = getUserFromDocument(row.id); + logger.info("Fetched user: " + fetchedUser); if (row.value != null) { AuthDBNameViewDesign nameViewDesign = gson.fromJson(gson.toJson(row.value), - AuthDBNameViewDesign.class); - revNumber = nameViewDesign._rev; + AuthDBNameViewDesign.class); + fetchedUser.setVersion(nameViewDesign._rev); // Set the version from the CouchDB rev } + // Assign fetchedUser to the user variable + user = fetchedUser; } + logger.info("User retrieved from CouchDB OK"); } catch (CouchdbException e) { @@ -239,82 +252,91 @@ public UserDoc getUserByLoginId(String loginId) throws AuthStoreException { throw new AuthStoreException(errorMessage, e); } - // Always going to return one entry, as loginIds are unique. - // Hence, we can take out the first index - if (!users.isEmpty()) { - - user = users.get(0); - user.setUserNumber(userDocument.get(0).id); - user.setVersion(revNumber); - - } - return user; - } @Override - public void updateUserClientActivity(String loginId, String clientName) throws AuthStoreException { + public IUser updateUser(IUser user) throws AuthStoreException { + // Take a clone of the user we are passed, so we can guarantee we are using our bean which + // serialises to the correct format. + UserDoc userDoc = new UserDoc(user); + updateUserDoc(httpClient, storeUri, 0, userDoc); + return userDoc; + } - UserDoc user = getUserByLoginId(loginId); + private void updateUserDoc(CloseableHttpClient httpClient, URI couchdbUri, int attempts, UserDoc user) + throws AuthStoreException { - List clients = user.getClients(); + validateUserDoc(user); - Optional clientOptional = clients.stream() - .filter(client -> client.getClientName().equals(clientName)) - .findFirst(); + HttpEntityEnclosingRequestBase request = buildUpdateUserDocRequest(user, couchdbUri); - if (clientOptional.isPresent()) { - FrontendClient client = clientOptional.get(); - client.setLastLoggedIn(Instant.now()); - } else { - // User has used a new client. - FrontendClient newClient = new FrontendClient(clientName, Instant.now()); - clients.add(newClient); - } + PutPostResponse putResponse = sendPutRequestToCouchDb(request); + validateCouchdbResponseJson(user.getUserNumber(), putResponse); + + // The version of the document in couchdb has updated, so lets update our version in the doc we were sent, + // so that the same document could be used for another update. + user.setVersion(putResponse.rev); + } + + private PutPostResponse sendPutRequestToCouchDb(HttpEntityEnclosingRequestBase request) throws AuthStoreException { + PutPostResponse putResponse; try { - updateUserDoc(httpClient, storeUri, 0, user); + String entity = sendHttpRequest(request, HttpStatus.SC_CREATED); + putResponse = gson.fromJson(entity, PutPostResponse.class); + } catch (CouchdbException e) { - e.printStackTrace(); - throw new AuthStoreException(e); + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT.getMessage(e.getMessage()); + throw new AuthStoreException(errorMessage,e); } + return putResponse; } - private void updateUserDoc(CloseableHttpClient httpClient, URI couchdbUri, int attempts, UserDoc user) - throws CouchdbException { + private HttpEntityEnclosingRequestBase buildUpdateUserDocRequest(UserDoc user, URI couchdbUri) { + HttpEntityEnclosingRequestBase request; String jsonStructure = gson.toJson(user); - - HttpEntityEnclosingRequestBase request; - - if (user.getUserNumber() == null) { - request = httpRequestFactory.getHttpPostRequest(couchdbUri + "/" + USERS_DATABASE_NAME); - } else { - request = httpRequestFactory - .getHttpPutRequest(couchdbUri + "/" + USERS_DATABASE_NAME + "/" + user.getUserNumber()); - request.setHeader("If-Match", user.getVersion()); - - logger.info("Rev is: " + user.getVersion()); - } - + request = httpRequestFactory + .getHttpPutRequest(couchdbUri + "/" + USERS_DATABASE_NAME + "/" + user.getUserNumber()); + request.setHeader("If-Match", user.getVersion()); + request.setEntity(new StringEntity(jsonStructure, StandardCharsets.UTF_8)); + return request; + } - try { - String entity = sendHttpRequest(request, HttpStatus.SC_CREATED); - PutPostResponse putPostResponse = gson.fromJson(entity, PutPostResponse.class); - - if (putPostResponse.id == null || putPostResponse.rev == null) { - throw new CouchdbException("Unable to store the user structure - Invalid JSON response"); - } + private void validateCouchdbResponseJson(String expectedUserNumber , PutPostResponse putResponse) throws AuthStoreException { + if (putResponse.id == null || putResponse.rev == null) { + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INVALID_RESP.getMessage(); + throw new AuthStoreException(errorMessage); + } + if (!expectedUserNumber.equals(putResponse.id)) { + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_MISMATCH_DOC_ID.getMessage(); + throw new AuthStoreException(errorMessage); + } + } - user.setUserNumber(putPostResponse.id); - user.setVersion(putPostResponse.rev); + private void validateUserDoc(UserDoc user) throws AuthStoreException { + if (user.getUserNumber() == null) { + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_NUMBER.getMessage(); + throw new AuthStoreException(errorMessage); + } + + if (user.getVersion() == null) { + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_VERSION.getMessage(); + throw new AuthStoreException(errorMessage); + } + } - } catch (CouchdbException e) { - throw new CouchdbException(e); - } + @Override + public void deleteUser(IUser user) throws AuthStoreException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'deleteUser'"); + } + @Override + public IFrontEndClient createClient(String clientName) { + return new FrontEndClient(clientName, timeService.now()); } /** diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java index a9378019..cbcfd03f 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java @@ -97,7 +97,7 @@ public void checkTokensDesignDocument(CloseableHttpClient httpClient, URI couchd AuthDBNameViewDesign tableDesign = parseTokenDesignFromJson(docJson); - boolean isDesignUpdated = updateDesignDocToDesiredDesignDoc(tableDesign); + boolean isDesignUpdated = updateDesignDocToDesiredDesignDoc(tableDesign, dbName); if (isDesignUpdated) { updateTokenDesignDocument(httpClient, couchdbUri, attempts, tableDesign, dbName); @@ -118,23 +118,27 @@ private AuthDBNameViewDesign parseTokenDesignFromJson(String docJson) throws Cou return tableDesign; } - private boolean updateDesignDocToDesiredDesignDoc(AuthDBNameViewDesign tableDesign) { + private boolean updateDesignDocToDesiredDesignDoc(AuthDBNameViewDesign tableDesign, String dbName) { boolean isUpdated = false; if (tableDesign.views == null) { isUpdated = true; - tableDesign.views = new TokenDBViews(); + tableDesign.views = new AuthStoreDBViews(); } if (tableDesign.views.loginIdView == null) { isUpdated = true; - tableDesign.views.loginIdView = new TokenDBLoginView(); + tableDesign.views.loginIdView = new AuthStoreDBLoginView(); } - if (tableDesign.views.loginIdView.map == null - || !DB_TABLE_TOKENS_DESIGN.equals(tableDesign.views.loginIdView.map)) { + if (tableDesign.views.loginIdView.map == null) { isUpdated = true; - tableDesign.views.loginIdView.map = DB_TABLE_TOKENS_DESIGN; + if(dbName.equals("galasa_tokens")){ + tableDesign.views.loginIdView.map = DB_TABLE_TOKENS_DESIGN; + } + else{ + tableDesign.views.loginIdView.map = DB_TABLE_USERS_DESIGN; + } } if (tableDesign.language == null || !tableDesign.language.equals("javascript")) { diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java index 8a508382..77180e21 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthDBNameViewDesign.java @@ -9,10 +9,10 @@ public class AuthDBNameViewDesign { public String _rev; public String _id; - public TokenDBViews views; + public AuthStoreDBViews views; public String language; - public AuthDBNameViewDesign(String _rev, String _id, TokenDBViews views, String language) { + public AuthDBNameViewDesign(String _rev, String _id, AuthStoreDBViews views, String language) { this._rev = _rev; this._id = _id; this.views = views; diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokenDBLoginView.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthStoreDBLoginView.java similarity index 82% rename from galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokenDBLoginView.java rename to galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthStoreDBLoginView.java index 95c92afe..53b4fe8c 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokenDBLoginView.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthStoreDBLoginView.java @@ -5,6 +5,6 @@ */ package dev.galasa.auth.couchdb.internal.beans; -public class TokenDBLoginView { +public class AuthStoreDBLoginView { public String map; } \ No newline at end of file diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokenDBViews.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthStoreDBViews.java similarity index 75% rename from galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokenDBViews.java rename to galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthStoreDBViews.java index d7af7ffb..a92c6567 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/TokenDBViews.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/AuthStoreDBViews.java @@ -7,7 +7,7 @@ import com.google.gson.annotations.SerializedName; -public class TokenDBViews { +public class AuthStoreDBViews { @SerializedName("loginId-view") - public TokenDBLoginView loginIdView; + public AuthStoreDBLoginView loginIdView; } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java new file mode 100644 index 00000000..cb3085b2 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java @@ -0,0 +1,55 @@ +package dev.galasa.auth.couchdb.internal.beans; + +import com.google.gson.annotations.SerializedName; + +import dev.galasa.framework.spi.auth.IFrontEndClient; +import java.time.Instant; + +public class FrontEndClient implements IFrontEndClient{ + + @SerializedName("client-name") + private String clientName; + + @SerializedName("last-login") + private Instant lastLogin; + + // No-arg constructor + public FrontEndClient() {} + + // Parameterized constructor + public FrontEndClient(String clientName, Instant lastLoggedIn) { + this.clientName = clientName; + this.lastLogin = lastLoggedIn; + } + + public FrontEndClient(IFrontEndClient fClient){ + this.clientName = fClient.getClientName(); + this.lastLogin = fClient.getLastLogin(); + } + + // Getter and Setter for lastLoggedIn + public Instant getLastLogin() { + return lastLogin; + } + + @Override + public void setLastLogin(Instant lastLoginTime) { + this.lastLogin = lastLoginTime; + } + + // Getter and Setter for clientName + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + // toString method to display client details + @Override + public String toString() { + return "Client [clientName=" + clientName + ", lastLoggedIn=" + lastLogin + "]"; + } + +} diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java new file mode 100644 index 00000000..c8005052 --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java @@ -0,0 +1,114 @@ +package dev.galasa.auth.couchdb.internal.beans; + +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; + + +import com.google.gson.annotations.SerializedName; + +import dev.galasa.framework.spi.auth.IFrontEndClient; +import dev.galasa.framework.spi.auth.IUser; + +public class UserDoc implements IUser{ + + @SerializedName("_id") + private String userNumber; + + @SerializedName("_rev") + private String version; + + @SerializedName("login-id") + private String loginId; + + @SerializedName("activity") + private List clients; + + public UserDoc(String loginId, List clients) { + this.loginId = loginId; + setClients( clients); + } + + public UserDoc(IUser user){ + this.loginId = user.getLoginId(); + this.version = user.getVersion(); + this.userNumber = user.getUserNumber(); + setClients(user.getClients()); + } + + @Override + public String getUserNumber(){ + return userNumber; + } + + public void setUserNumber(String userNumber){ + this.userNumber = userNumber; + } + + public String getVersion(){ + return version; + } + + public void setVersion(String version){ + this.version = version; + } + + @Override + public String getLoginId() { + return loginId; + } + + public void setLoginId(String loginId) { + this.loginId = loginId; + } + + @Override + public Collection getClients() { + Collection results = new ArrayList(); + for( FrontEndClient client : this.clients) { + results.add(client); + } + return results; + } + + public void setClients(List clients) { + this.clients = new ArrayList(); + if( clients != null) { + for (IFrontEndClient clientIn: clients) { + addClient(clientIn); + } + } + } + + // Setter for clients. Takes a deep copy of any clients it is passed. + public void setClients(Collection clients) { + + this.clients = new ArrayList(); + if( clients != null) { + for (IFrontEndClient clientIn: clients) { + addClient(clientIn); + } + } + } + + @Override + public IFrontEndClient getClient(String clientName) { + IFrontEndClient match = null; + if (clientName != null) { + for (FrontEndClient frontEndClient : clients) { + if(clientName.equals(frontEndClient.getClientName())){ + match = frontEndClient; + break; + } + } + } + return match; + } + + @Override + public void addClient(IFrontEndClient client) { + clients.add( new FrontEndClient(client)); + } + + +} diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java similarity index 64% rename from galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java rename to galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java index 4faf6e28..00363247 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package dev.galasa.auth.couchdb; +package dev.galasa.auth.couchdb.internal; import static org.assertj.core.api.Assertions.*; @@ -17,11 +17,8 @@ import org.apache.http.HttpStatus; import org.junit.Test; -import dev.galasa.auth.couchdb.internal.CouchdbAuthStore; -import dev.galasa.auth.couchdb.internal.CouchdbAuthToken; -import dev.galasa.auth.couchdb.internal.CouchdbUser; -import dev.galasa.auth.couchdb.internal.beans.TokenDBViews; -import dev.galasa.auth.couchdb.internal.beans.AuthDBNameViewDesign; +import dev.galasa.auth.couchdb.internal.beans.FrontEndClient; +import dev.galasa.auth.couchdb.internal.beans.UserDoc; import dev.galasa.extensions.common.couchdb.pojos.IdRev; import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewResponse; @@ -35,9 +32,9 @@ import dev.galasa.extensions.mocks.MockTimeService; import dev.galasa.extensions.mocks.couchdb.MockCouchdbValidator; import dev.galasa.framework.spi.auth.AuthStoreException; -import dev.galasa.framework.spi.auth.FrontendClient; +import dev.galasa.framework.spi.auth.IFrontEndClient; import dev.galasa.framework.spi.auth.IInternalAuthToken; -import dev.galasa.framework.spi.auth.UserDoc; +import dev.galasa.framework.spi.auth.IUser; public class TestCouchdbAuthStore { @@ -50,7 +47,7 @@ public GetAllDocumentsInteraction(String expectedUri, int responseStatusCode, Vi @Override public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { - super.validateRequest(host,request); + super.validateRequest(host, request); assertThat(request.getRequestLine().getMethod()).isEqualTo("GET"); } } @@ -64,7 +61,7 @@ public GetDocumentInteraction(String expectedUri, int responseStatusCode, T resp @Override public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { - super.validateRequest(host,request); + super.validateRequest(host, request); assertThat(request.getRequestLine().getMethod()).isEqualTo("GET"); } } @@ -78,7 +75,7 @@ public DeleteTokenDocumentInteraction(String expectedUri, int responseStatusCode @Override public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { - super.validateRequest(host,request); + super.validateRequest(host, request); assertThat(request.getRequestLine().getMethod()).isEqualTo("DELETE"); } } @@ -97,26 +94,26 @@ public CreateDocumentInteraction(String expectedUri, int responseStatusCode) { @Override public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { - super.validateRequest(host,request); + super.validateRequest(host, request); assertThat(request.getRequestLine().getMethod()).isEqualTo("POST"); } } class UpdateDocumentInteraction extends BaseHttpInteraction { - public UpdateDocumentInteraction(String expectedUri, int responseStatusCode) { + public UpdateDocumentInteraction(String expectedUri, int responseStatusCode, String id, String rev) { super(expectedUri, responseStatusCode); PutPostResponse responseTransportBean = new PutPostResponse(); - responseTransportBean.id = "id"; + responseTransportBean.id = id; responseTransportBean.ok = true; - responseTransportBean.rev = "rev"; + responseTransportBean.rev = rev; setResponsePayload(responseTransportBean); } @Override public void validateRequest(HttpHost host, HttpRequest request) throws RuntimeException { - super.validateRequest(host,request); + super.validateRequest(host, request); assertThat(request.getRequestLine().getMethod()).isEqualTo("PUT"); } } @@ -128,14 +125,16 @@ public void testGetTokensReturnsTokensWithFailingRequestReturnsErrorDup() throws MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", + HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... AuthStoreException thrown = catchThrowableOfType(() -> authStore.getTokens(), AuthStoreException.class); @@ -158,17 +157,21 @@ public void testGetTokensReturnsTokensFromCouchdbOK() throws Exception { ViewResponse mockAllDocsResponse = new ViewResponse(); mockAllDocsResponse.rows = mockDocs; - CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), new CouchdbUser("johndoe", "dex-user-id")); + CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), + new CouchdbUser("johndoe", "dex-user-id")); List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); + interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_all_docs", + HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", + HttpStatus.SC_OK, mockToken)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... List tokens = authStore.getTokens(); @@ -193,19 +196,26 @@ public void testGetTokensReturnsTokensByLoginIdFromCouchdbOK() throws Exception ViewResponse mockAllDocsResponse = new ViewResponse(); mockAllDocsResponse.rows = mockDocs; - CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), new CouchdbUser("johndoe", "dex-user-id")); - CouchdbAuthToken mockToken2 = new CouchdbAuthToken("token2", "dex-client", "my test token", Instant.now(), new CouchdbUser("notJohnDoe", "dex-user-id")); + CouchdbAuthToken mockToken = new CouchdbAuthToken("token1", "dex-client", "my test token", Instant.now(), + new CouchdbUser("johndoe", "dex-user-id")); + CouchdbAuthToken mockToken2 = new CouchdbAuthToken("token2", "dex-client", "my test token", Instant.now(), + new CouchdbUser("notJohnDoe", "dex-user-id")); List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", HttpStatus.SC_OK, mockToken2)); + interactions.add(new GetAllDocumentsInteraction( + "https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", + HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", + HttpStatus.SC_OK, mockToken)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_tokens/token1", + HttpStatus.SC_OK, mockToken2)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... List tokens = authStore.getTokensByLoginId("johndoe"); @@ -223,17 +233,21 @@ public void testGetTokensReturnsTokensByLoginIdWithFailingRequestReturnsError() MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); + interactions.add(new GetAllDocumentsInteraction( + "https://my-auth-store/galasa_tokens/_design/docs/_view/loginId-view?key=%22johndoe%22", + HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... - AuthStoreException thrown = catchThrowableOfType(() -> authStore.getTokensByLoginId("johndoe"), AuthStoreException.class); + AuthStoreException thrown = catchThrowableOfType(() -> authStore.getTokensByLoginId("johndoe"), + AuthStoreException.class); // Then... assertThat(thrown).isNotNull(); @@ -254,12 +268,14 @@ public void testStoreTokenSendsRequestToCreateTokenDocumentOK() throws Exception MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... authStore.storeToken("this-is-a-dex-id", "my token", new CouchdbUser("user1", "user1-id")); - // Then the assertions made in the create token document interaction shouldn't have failed. + // Then the assertions made in the create token document interaction shouldn't + // have failed. } @Test @@ -269,21 +285,26 @@ public void testStoreTokenWithFailingRequestToCreateTokenDocumentReturnsError() MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new CreateDocumentInteraction("https://my-auth-store/galasa_tokens", HttpStatus.SC_INTERNAL_SERVER_ERROR)); + interactions.add(new CreateDocumentInteraction("https://my-auth-store/galasa_tokens", + HttpStatus.SC_INTERNAL_SERVER_ERROR)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... - AuthStoreException thrown = catchThrowableOfType(() -> authStore.storeToken("this-is-a-dex-id", "my token", new CouchdbUser("user1", "user1-id")), AuthStoreException.class); + AuthStoreException thrown = catchThrowableOfType( + () -> authStore.storeToken("this-is-a-dex-id", "my token", new CouchdbUser("user1", "user1-id")), + AuthStoreException.class); // Then... assertThat(thrown).isNotNull(); - assertThat(thrown.getMessage()).contains("GAL6102E", "Failed to store auth token in the CouchDB tokens database"); + assertThat(thrown.getMessage()).contains("GAL6102E", + "Failed to store auth token in the CouchDB tokens database"); } @Test @@ -296,9 +317,10 @@ public void testDeleteTokenSendsRequestToDeleteTokenDocumentOK() throws Exceptio IdRev mockIdRev = new IdRev(); mockIdRev._rev = "this-is-a-revision"; - + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; - String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; + String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + + mockIdRev._rev; List interactions = new ArrayList(); interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); @@ -309,7 +331,8 @@ public void testDeleteTokenSendsRequestToDeleteTokenDocumentOK() throws Exceptio MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... authStore.deleteToken(tokenIdToDelete); @@ -327,14 +350,16 @@ public void testDeleteTokenWithAcceptedRequestToDeleteTokenDocumentDoesNotError( IdRev mockIdRev = new IdRev(); mockIdRev._rev = "this-is-a-revision"; - + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; - String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; + String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + + mockIdRev._rev; List interactions = new ArrayList(); interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); - // The DELETE request may return a 202 Accepted, which shouldn't be a problem for us + // The DELETE request may return a 202 Accepted, which shouldn't be a problem + // for us interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_ACCEPTED)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); @@ -342,7 +367,8 @@ public void testDeleteTokenWithAcceptedRequestToDeleteTokenDocumentDoesNotError( MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... authStore.deleteToken(tokenIdToDelete); @@ -360,22 +386,25 @@ public void testDeleteTokenWithFailingRequestToDeleteTokenDocumentThrowsError() IdRev mockIdRev = new IdRev(); mockIdRev._rev = "this-is-a-revision"; - + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; - String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + mockIdRev._rev; + String expectedDeleteRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete + "?rev=" + + mockIdRev._rev; List interactions = new ArrayList(); interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_OK, mockIdRev)); // Simulate an internal server error - interactions.add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR)); + interactions + .add(new DeleteTokenDocumentInteraction(expectedDeleteRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... AuthStoreException thrown = catchThrowableOfType(() -> { @@ -384,7 +413,8 @@ public void testDeleteTokenWithFailingRequestToDeleteTokenDocumentThrowsError() // Then... assertThat(thrown).isNotNull(); - assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); + assertThat(thrown.getMessage()).contains("GAL6104E", + "Failed to delete auth token from the CouchDB tokens database"); assertThat(thrown.getMessage()).contains("GAL6007E", "Expected status code(s) [200, 202] but received 500"); } @@ -398,20 +428,22 @@ public void testDeleteTokenWithFailingRequestToGetTokenDocumentThrowsError() thr IdRev mockIdRev = new IdRev(); mockIdRev._rev = "this-is-a-revision"; - + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; List interactions = new ArrayList(); // Simulate an internal server error - interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR, mockIdRev)); + interactions.add(new GetDocumentInteraction(expectedGetRequestUrl, HttpStatus.SC_INTERNAL_SERVER_ERROR, + mockIdRev)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... AuthStoreException thrown = catchThrowableOfType(() -> { @@ -420,7 +452,8 @@ public void testDeleteTokenWithFailingRequestToGetTokenDocumentThrowsError() thr // Then... assertThat(thrown).isNotNull(); - assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); + assertThat(thrown.getMessage()).contains("GAL6104E", + "Failed to delete auth token from the CouchDB tokens database"); } @Test @@ -430,7 +463,7 @@ public void testDeleteTokenWithBadGetTokenDocumentResponseBodyThrowsError() thro MockLogFactory logFactory = new MockLogFactory(); String tokenIdToDelete = "my-old-token"; - + String expectedGetRequestUrl = "https://my-auth-store/galasa_tokens/" + tokenIdToDelete; List interactions = new ArrayList(); @@ -443,7 +476,8 @@ public void testDeleteTokenWithBadGetTokenDocumentResponseBodyThrowsError() thro MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... AuthStoreException thrown = catchThrowableOfType(() -> { @@ -452,12 +486,14 @@ public void testDeleteTokenWithBadGetTokenDocumentResponseBodyThrowsError() thro // Then... assertThat(thrown).isNotNull(); - assertThat(thrown.getMessage()).contains("GAL6104E", "Failed to delete auth token from the CouchDB tokens database"); - assertThat(thrown.getMessage()).contains("GAL6011E", "Failed to get document with ID 'my-old-token' from the 'galasa_tokens' database"); + assertThat(thrown.getMessage()).contains("GAL6104E", + "Failed to delete auth token from the CouchDB tokens database"); + assertThat(thrown.getMessage()).contains("GAL6011E", + "Failed to get document with ID 'my-old-token' from the 'galasa_tokens' database"); } @Test - public void testGetAllUsersReturnsListOfUsersFroasmCouchdbOK() throws Exception { + public void testGetAllUsersReturnsListOfUsersFromCouchdbOK() throws Exception { // Given... URI authStoreUri = URI.create("couchdb:https://my-users-store"); MockLogFactory logFactory = new MockLogFactory(); @@ -468,7 +504,7 @@ public void testGetAllUsersReturnsListOfUsersFroasmCouchdbOK() throws Exception userDoc.id = "user1"; userDoc2.id = "user2"; - List mockDocs = List.of(userDoc,userDoc2); + List mockDocs = List.of(userDoc, userDoc2); ViewResponse mockAllDocsResponse = new ViewResponse(); mockAllDocsResponse.rows = mockDocs; @@ -476,18 +512,23 @@ public void testGetAllUsersReturnsListOfUsersFroasmCouchdbOK() throws Exception UserDoc mockUser = new UserDoc("user1", List.of()); UserDoc mockUser2 = new UserDoc("user2", List.of()); List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-users-store/galasa_users/_all_docs", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-users-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); - interactions.add(new GetDocumentInteraction("https://my-users-store/galasa_users/user2", HttpStatus.SC_OK, mockUser2)); + interactions.add(new GetAllDocumentsInteraction("https://my-users-store/galasa_users/_all_docs", + HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-users-store/galasa_users/user1", + HttpStatus.SC_OK, mockUser)); + interactions.add(new GetDocumentInteraction("https://my-users-store/galasa_users/user2", + HttpStatus.SC_OK, mockUser2)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, + httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), + mockTimeService); // When... - List users = authStore.getAllUsers(); + List users = authStore.getAllUsers(); // Then... assertThat(users).hasSize(2); @@ -500,25 +541,28 @@ public void testGetTokensReturnsTokensWithFailingRequestReturnsError() throws Ex MockLogFactory logFactory = new MockLogFactory(); List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-users-store/galasa_users/_all_docs", HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); + interactions.add(new GetAllDocumentsInteraction("https://my-users-store/galasa_users/_all_docs", + HttpStatus.SC_INTERNAL_SERVER_ERROR, null)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... AuthStoreException thrown = catchThrowableOfType(() -> authStore.getAllUsers(), AuthStoreException.class); // Then... assertThat(thrown).isNotNull(); - assertThat(thrown.getMessage()).contains("GAL6202E", "Failed to get user documents from the CouchDB users store."); + assertThat(thrown.getMessage()).contains("GAL6202E", + "Failed to get user documents from the CouchDB users store."); } @Test - public void testStoreTokenSendsRequestToasCreateTokenDocumentOK() throws Exception { + public void testStoreUserSendsRequestToCreateUserDocumentOK() throws Exception { // Given... URI authStoreUri = URI.create("couchdb:https://my-users-store"); MockLogFactory logFactory = new MockLogFactory(); @@ -531,12 +575,14 @@ public void testStoreTokenSendsRequestToasCreateTokenDocumentOK() throws Excepti MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... authStore.createUser("this-is-a-login-id", "rest-api"); - // Then the assertions made in the create users document interaction shouldn't have failed. + // Then the assertions made in the create users document interaction shouldn't + // have failed. } @Test @@ -553,22 +599,33 @@ public void testGetUserReturnsUsersByLoginIdFromCouchdbOK() throws Exception { ViewResponse mockAllDocsResponse = new ViewResponse(); mockAllDocsResponse.rows = mockDocs; - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); - + FrontEndClient client = new FrontEndClient(); + + client.setClientName("web-ui"); + client.setLastLogin(Instant.now()); + + UserDoc mockUser = new UserDoc("johndoe", List.of(client)); + List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + interactions.add(new GetAllDocumentsInteraction( + "https://my-auth-store/galasa_users/_design/docs/_view/loginId-view?key=%22user1%22", + HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", + HttpStatus.SC_OK, mockUser)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), + logFactory, new MockCouchdbValidator(), mockTimeService); // When... - UserDoc user = authStore.getUserByLoginId("johndoe"); + IUser user = authStore.getUserByLoginId("user1"); + assertThat(user).isInstanceOf(UserDoc.class); assertThat(user).isNotNull(); + assertThat(user.getLoginId()).isEqualTo("johndoe"); } @Test @@ -585,20 +642,25 @@ public void testGetUserReturnsCorrectUserByLoginIdFromCouchdbOK() throws Excepti ViewResponse mockAllDocsResponse = new ViewResponse(); mockAllDocsResponse.rows = mockDocs; - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); - + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("web-ui", Instant.now()))); + List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22notJohndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); + interactions.add(new GetAllDocumentsInteraction( + "https://my-auth-store/galasa_users/_design/docs/_view/loginId-view?key=%22notJohndoe%22", + HttpStatus.SC_OK, mockAllDocsResponse)); + interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", + HttpStatus.SC_OK, mockUser)); MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); MockTimeService mockTimeService = new MockTimeService(Instant.now()); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, + httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), + mockTimeService); // When... - UserDoc user = authStore.getUserByLoginId("notJohndoe"); + IUser user = authStore.getUserByLoginId("notJohndoe"); assertThat(user).usingRecursiveComparison().isNotEqualTo(mockUser); } @@ -606,68 +668,152 @@ public void testGetUserReturnsCorrectUserByLoginIdFromCouchdbOK() throws Excepti @Test public void testUpdateUserUpdatesExisitingClientOK() throws Exception { // Given... - URI authStoreUri = URI.create("couchdb:https://my-auth-store"); - MockLogFactory logFactory = new MockLogFactory(); + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("web-ui", Instant.MIN))); + mockUser.setVersion("1"); + mockUser.setUserNumber("user1"); - ViewRow userDoc1 = new ViewRow(); + List interactions = new ArrayList(); + interactions.add(new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", + HttpStatus.SC_CREATED, "user1", "2" )); - userDoc1.id = "user1"; - List mockDocs = List.of(userDoc1); + CouchdbAuthStore authStore = createAuthStoreToTest(interactions); - ViewResponse mockAllDocsResponse = new ViewResponse(); - mockAllDocsResponse.rows = mockDocs; + IUser userGotBack = authStore.updateUser(mockUser); + + assertThat(userGotBack.getVersion()).isEqualTo("2"); + } + + @Test + public void testUpdateUserCouchDBPassesBackANullVersionField() throws Exception { + // Given... + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + mockUser.setVersion("1"); + mockUser.setUserNumber("user1"); - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); - List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); - interactions.add(new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED)); + interactions.add( + new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED, "user1" , null ) + ); - MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + CouchdbAuthStore authStore = createAuthStoreToTest(interactions); - MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); - MockTimeService mockTimeService = new MockTimeService(Instant.now()); + AuthStoreException ex = catchThrowableOfType( ()-> authStore.updateUser(mockUser), AuthStoreException.class); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); - - authStore.updateUserClientActivity("johndoe", "web-ui"); + assertThat(ex.getMessage()).contains("GAL6204E"); + } - // Then the assertions made in the updates users document interaction shouldn't have failed. + @Test + public void testUpdateUserCouchDBPassesBackADocIdWithWrongUserNumberField() throws Exception { + // Given... + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + mockUser.setVersion("1"); + mockUser.setUserNumber("user1"); + + List interactions = new ArrayList(); + interactions.add( + new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED, "user2" , "2" ) + ); + + CouchdbAuthStore authStore = createAuthStoreToTest(interactions); + + AuthStoreException ex = catchThrowableOfType( ()-> authStore.updateUser(mockUser), AuthStoreException.class); + + assertThat(ex.getMessage()).contains("GAL6205E"); } @Test - public void testUpdateUserAddsNewClientToUserOK() throws Exception { + public void testUpdateUserCouchDBPassesBackADocIdWithMissingIdField() throws Exception { // Given... - URI authStoreUri = URI.create("couchdb:https://my-auth-store"); - MockLogFactory logFactory = new MockLogFactory(); - ViewRow userDoc1 = new ViewRow(); + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + mockUser.setVersion("1"); + mockUser.setUserNumber("user1"); - userDoc1.id = "user1"; - userDoc1.key = "admin"; - userDoc1.value = new AuthDBNameViewDesign("hello", "world", new TokenDBViews(), "javascript"); - List mockDocs = List.of(userDoc1); + List interactions = new ArrayList(); + interactions.add( + new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED,null , "2" ) + ); - ViewResponse mockAllDocsResponse = new ViewResponse(); - mockAllDocsResponse.rows = mockDocs; + CouchdbAuthStore authStore = createAuthStoreToTest(interactions); + + AuthStoreException ex = catchThrowableOfType( ()-> authStore.updateUser(mockUser), AuthStoreException.class); + + assertThat(ex.getMessage()).contains("GAL6204E"); + } + + @Test + public void testUpdateUserCouchDBPassesBackAnUnexpectedServerError() throws Exception { + // Given... + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + mockUser.setVersion("1"); + mockUser.setUserNumber("user1"); - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontendClient("web-ui", Instant.now()))); - List interactions = new ArrayList(); - interactions.add(new GetAllDocumentsInteraction("https://my-auth-store/galasa_users/_design/docs/_view/users-loginId-view?key=%22johndoe%22", HttpStatus.SC_OK, mockAllDocsResponse)); - interactions.add(new GetDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_OK, mockUser)); - interactions.add(new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_CREATED)); + interactions.add( + new UpdateDocumentInteraction("https://my-auth-store/galasa_users/user1", HttpStatus.SC_INTERNAL_SERVER_ERROR,null , "2" ) + ); - MockCloseableHttpClient mockHttpClient = new MockCloseableHttpClient(interactions); + CouchdbAuthStore authStore = createAuthStoreToTest(interactions); - MockHttpClientFactory httpClientFactory = new MockHttpClientFactory(mockHttpClient); - MockTimeService mockTimeService = new MockTimeService(Instant.now()); + AuthStoreException ex = catchThrowableOfType( ()-> authStore.updateUser(mockUser), AuthStoreException.class); + + assertThat(ex.getMessage()).contains("GAL6203E"); + } + + @Test + public void testUpdateUserWithBadUserNullIdFieldGetsDetectedAsError() throws Exception { + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + mockUser.setVersion("1"); + mockUser.setUserNumber(null); + + CouchdbAuthStore authStore = createAuthStoreToTest(); + + AuthStoreException ex = catchThrowableOfType( ()-> authStore.updateUser(mockUser), AuthStoreException.class); - CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbValidator(), mockTimeService); - - authStore.updateUserClientActivity("johndoe", "rest-api"); + assertThat(ex.getMessage()).contains("GAL6206E"); + } + + @Test + public void testUpdateUserWithBadUserNullVersionFieldGetsDetectedAsError() throws Exception { + UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + mockUser.setVersion(null); + mockUser.setUserNumber("user1"); + + CouchdbAuthStore authStore = createAuthStoreToTest(); + + AuthStoreException ex = catchThrowableOfType( ()-> authStore.updateUser(mockUser), AuthStoreException.class); + + assertThat(ex.getMessage()).contains("GAL6207E"); + } + + private CouchdbAuthStore createAuthStoreToTest() throws Exception { + List interactions = new ArrayList(); + return createAuthStoreToTest(interactions); + } - // Then the assertions made in the updates users document interaction shouldn't have failed. + private CouchdbAuthStore createAuthStoreToTest(List interactions) throws Exception { + URI authStoreUri = URI.create("couchdb:https://my-auth-store"); + MockLogFactory logFactory = new MockLogFactory(); + + MockCloseableHttpClient mockHttpClient = new + MockCloseableHttpClient(interactions); + + MockHttpClientFactory httpClientFactory = new + MockHttpClientFactory(mockHttpClient); + MockTimeService mockTimeService = new MockTimeService(Instant.MIN); + + CouchdbAuthStore authStore = new CouchdbAuthStore(authStoreUri, + httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new + MockCouchdbValidator(), mockTimeService); + + return authStore; + } + + @Test + public void testCanCreateAClientDocument() throws Exception { + CouchdbAuthStore authStore = createAuthStoreToTest(); + IFrontEndClient client = authStore.createClient("myClientName"); + assertThat(client.getClientName()).isEqualTo("myClientName"); + assertThat(client.getLastLogin()).isEqualTo(Instant.MIN); } } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreRegistration.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStoreRegistration.java similarity index 95% rename from galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreRegistration.java rename to galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStoreRegistration.java index 260c1eb3..13083c34 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreRegistration.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStoreRegistration.java @@ -3,12 +3,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package dev.galasa.auth.couchdb; +package dev.galasa.auth.couchdb.internal; import org.junit.Test; -import dev.galasa.auth.couchdb.internal.CouchdbAuthStore; -import dev.galasa.auth.couchdb.internal.CouchdbAuthStoreRegistration; import dev.galasa.extensions.common.impl.HttpRequestFactoryImpl; import dev.galasa.extensions.mocks.MockFrameworkInitialisation; import dev.galasa.extensions.mocks.MockHttpClientFactory; diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStoreValidator.java similarity index 97% rename from galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java rename to galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStoreValidator.java index 4092c62a..e28a66c7 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/TestCouchdbAuthStoreValidator.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStoreValidator.java @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package dev.galasa.auth.couchdb; +package dev.galasa.auth.couchdb.internal; import static org.assertj.core.api.Assertions.*; @@ -18,8 +18,6 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.junit.Test; -import dev.galasa.auth.couchdb.internal.CouchdbAuthStore; -import dev.galasa.auth.couchdb.internal.CouchdbAuthStoreValidator; import dev.galasa.auth.couchdb.internal.beans.*; import dev.galasa.extensions.common.couchdb.CouchdbException; import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; @@ -135,9 +133,9 @@ public void testCheckCouchdbDatabaseIsValidWithValidDatabaseIsOK() throws Except // }, // "language": "javascript" // } - TokenDBLoginView view = new TokenDBLoginView(); + AuthStoreDBLoginView view = new AuthStoreDBLoginView(); view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; - TokenDBViews views = new TokenDBViews(); + AuthStoreDBViews views = new AuthStoreDBViews(); views.loginIdView = view; AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; @@ -208,9 +206,9 @@ public void testCheckCouchdbDatabaseIsValidWithSuccessfulDatabaseCreationIsOK() interactions.add(new GetTokensDatabaseInteraction(couchdbUriStr + "/" + usersDatabaseName, HttpStatus.SC_NOT_FOUND)); interactions.add(new CreateDatabaseInteraction(couchdbUriStr + "/" + usersDatabaseName, HttpStatus.SC_CREATED)); - TokenDBLoginView view = new TokenDBLoginView(); + AuthStoreDBLoginView view = new AuthStoreDBLoginView(); view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; - TokenDBViews views = new TokenDBViews(); + AuthStoreDBViews views = new AuthStoreDBViews(); views.loginIdView = view; AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; @@ -446,9 +444,9 @@ public void testCheckCouchdbDatabaseIsValidWithFailedDesignDocResponseThrowsErro // }, // "language": "javascript" // } - TokenDBLoginView view = new TokenDBLoginView(); + AuthStoreDBLoginView view = new AuthStoreDBLoginView(); view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; - TokenDBViews views = new TokenDBViews(); + AuthStoreDBViews views = new AuthStoreDBViews(); views.loginIdView = view; AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; @@ -497,9 +495,9 @@ public void testCheckCouchdbDatabaseIsValidWithUpdateDesignDocResponseThrowsErro // }, // "language": "javascript" // } - TokenDBLoginView view = new TokenDBLoginView(); + AuthStoreDBLoginView view = new AuthStoreDBLoginView(); view.map = "function (doc) {\n if (doc.owner && doc.owner.loginId) {\n emit(doc.owner.loginId, doc);\n }\n}"; - TokenDBViews views = new TokenDBViews(); + AuthStoreDBViews views = new AuthStoreDBViews(); views.loginIdView = view; AuthDBNameViewDesign designDocToPassBack = new AuthDBNameViewDesign(); designDocToPassBack.language = "javascript"; diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java new file mode 100644 index 00000000..7276035a --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java @@ -0,0 +1,278 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package dev.galasa.auth.couchdb.internal.beans; + +import static org.assertj.core.api.Assertions.*; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Test; + +import dev.galasa.framework.spi.auth.IFrontEndClient; +import dev.galasa.framework.spi.auth.IUser; + + +public class TestUserDoc { + + @Test + public void testCanConstructUserDocGivenAUserDoc() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId",null); + assertThat(doc1.getLoginId()).isEqualTo("myLoginId"); + } + + @Test + public void testCanConstructUserDocGivenAUserDocWithClient() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + + IFrontEndClient clientGotBack = doc1.getClient("myClient1"); + assertThat(clientGotBack).isNotNull(); + assertThat(clientGotBack.getClientName()).isEqualTo("myClient1"); + assertThat(clientGotBack.getLastLogin()).isEqualTo(Instant.MIN); + } + + @Test + public void testCanLookForClientWhichIsntInTheUserDocReturnsNull() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + + IFrontEndClient clientGotBack = doc1.getClient("myClient2"); // Client2 isn't there! + assertThat(clientGotBack).isNull(); + } + + @Test + public void testCanTryLookingForClientWithNullNameShouldReturnNull() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + + IFrontEndClient clientGotBack = doc1.getClient(null); + assertThat(clientGotBack).isNull(); + } + + class MockIUser implements IUser { + private String userNumber; + private String loginId; + + private List clients; + + public MockIUser(String loginId, List clients) { + this.loginId = loginId; + this.clients = clients; + } + + @Override + public String getUserNumber(){ + return userNumber; + } + + @Override + public String getLoginId() { + return loginId; + } + + @Override + public IFrontEndClient getClient(String clientName) { + return null; + } + + @Override + public Collection getClients() { + return this.clients; + } + + @Override + public void addClient(IFrontEndClient client) { + clients.add( new FrontEndClient(client)); + } + + @Override + public String getVersion() { + return "0" ; + } + + } + + @Test + public void testCanCloneUserDocFromIUser() throws Exception { + MockIUser doc1 = new MockIUser("myLoginId",null); + + UserDoc doc2 = new UserDoc(doc1); + + assertThat(doc2).isNotNull(); + assertThat(doc2.getLoginId()).isEqualTo("myLoginId"); + } + + class MockIFrontEndClient implements IFrontEndClient { + + String name ; + Instant lastLogin ; + + public MockIFrontEndClient(String name, Instant lastLogin) { + this.name = name; + this.lastLogin = lastLogin; + } + + @Override + public String getClientName() { + return name; + } + + @Override + public Instant getLastLogin() { + return lastLogin; + } + + @Override + public void setLastLogin(Instant lastLoginTime) { + } + + } + + @Test + public void testCanSetIFrontEndClientsInAndGetThemBack() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + MockIFrontEndClient mockClient = new MockIFrontEndClient("client2", Instant.MIN.plusSeconds(2)); + + doc1.addClient(mockClient); + + + // Then... + IFrontEndClient gotBack = doc1.getClient("client2"); + assertThat(gotBack).isNotNull(); + assertThat(gotBack.getLastLogin()).isEqualTo(Instant.MIN.plusSeconds(2)); + } + + @Test + public void testCanGetClientsWhenClientsArePresent() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of( + new FrontEndClient("client1", Instant.MIN), + new FrontEndClient("client2", Instant.now()) + )); + + Collection clients = doc1.getClients(); + + assertThat(clients).isNotNull(); + assertThat(clients).hasSize(2); + assertThat(clients).extracting("clientName").containsExactlyInAnyOrder("client1", "client2"); + } + + @Test + public void testCanGetClientsWhenNoClientsArePresent() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + + Collection clients = doc1.getClients(); + + assertThat(clients).isNotNull(); + assertThat(clients).isEmpty(); + } + + @Test + public void testCanSetAndGetUserNumber() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + doc1.setUserNumber("12345"); + + String userNumber = doc1.getUserNumber(); + + assertThat(userNumber).isNotNull(); + assertThat(userNumber).isEqualTo("12345"); + } + + @Test + public void testGetUserNumberReturnsNullWhenNotSet() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + + String userNumber = doc1.getUserNumber(); + + assertThat(userNumber).isNull(); + } + + @Test + public void testCanSetAndGetVersion() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + doc1.setVersion("1.0"); + + String version = doc1.getVersion(); + + assertThat(version).isNotNull(); + assertThat(version).isEqualTo("1.0"); + } + + @Test + public void testGetVersionReturnsNullWhenNotSet() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + + String version = doc1.getVersion(); + + assertThat(version).isNull(); + } + + @Test + public void testCanSetAndGetLoginId() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + doc1.setLoginId("newLoginId"); + + String loginId = doc1.getLoginId(); + + assertThat(loginId).isNotNull(); + assertThat(loginId).isEqualTo("newLoginId"); + } + + @Test + public void testGetLoginIdReturnsNullWhenNotSet() throws Exception { + UserDoc doc1 = new UserDoc(null, List.of()); + + String loginId = doc1.getLoginId(); + + assertThat(loginId).isNull(); + } + + @Test + public void testCanSetClientsSuccessfully() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + Collection clients = List.of( + new FrontEndClient("client1", Instant.MIN), + new FrontEndClient("client2", Instant.now()) + ); + + doc1.setClients(clients); + + Collection clientsGotBack = doc1.getClients(); + assertThat(clientsGotBack).isNotNull(); + assertThat(clientsGotBack).hasSize(2); + assertThat(clientsGotBack).extracting("clientName").containsExactlyInAnyOrder("client1", "client2"); + } + + @Test + public void testSetClientsDoesNotModifyOriginalCollection() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + List originalClients = new ArrayList<>(); + originalClients.add(new FrontEndClient("client1", Instant.MIN)); + + doc1.setClients(originalClients); + + // Modify the original collection after setting it + originalClients.add(new FrontEndClient("client2", Instant.now())); + + Collection clientsGotBack = doc1.getClients(); + assertThat(clientsGotBack).hasSize(1); // Should only contain the first client + assertThat(clientsGotBack).extracting("clientName").containsExactly("client1"); + } + + @Test + public void testSetClientsShouldInvokeAddClientForEachClient() throws Exception { + UserDoc doc1 = new UserDoc("myLoginId", List.of()); + Collection clients = List.of( + new FrontEndClient("client1", Instant.MIN), + new FrontEndClient("client2", Instant.now()) + ); + + doc1.setClients(clients); + + Collection clientsGotBack = doc1.getClients(); + assertThat(clientsGotBack).hasSize(2); + assertThat(clientsGotBack).extracting("clientName").contains("client1", "client2"); + } + +} diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java index eb6cabcb..85803443 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java @@ -36,9 +36,14 @@ public enum Errors { ERROR_FAILED_TO_DELETE_TOKEN_DOCUMENT (6104,"GAL6104E: Failed to delete auth token from the CouchDB tokens database. Cause: {0}"), // CouchDB Users Store error - ERROR_FAILED_TO_INITIALISE_USERS_STORE (6200,"GAL6200E: Failed to initialise the Galasa CouchDB users store. Cause: {0}"), - ERROR_FAILED_TO_CREATE_USER_DOCUMENT (6201,"GAL6201E: Failed to store users token in the CouchDB users database. Cause: {0}"), - ERROR_FAILED_TO_RETRIEVE_USERS (6202,"GAL6202E: Failed to get user documents from the CouchDB users store. Cause: {0}"), + ERROR_FAILED_TO_INITIALISE_USERS_STORE (6200,"GAL6200E: Failed to initialise the Galasa CouchDB users store. Cause: {0}"), + ERROR_FAILED_TO_CREATE_USER_DOCUMENT (6201,"GAL6201E: Failed to store users token in the CouchDB users database. Cause: {0}"), + ERROR_FAILED_TO_RETRIEVE_USERS (6202,"GAL6202E: Failed to get user documents from the CouchDB users store. Cause: {0}"), + ERROR_FAILED_TO_UPDATE_USER_DOCUMENT (6203,"GAL6203E: Failed to update user document in the CouchDB users store. Cause: {0}"), + ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INVALID_RESP (6204,"GAL6204E: Failed to update user document in the CouchDB users store. Cause: Couchdb returned an unexpected json response with no _rev or _id field."), + ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_MISMATCH_DOC_ID (6205,"GAL6205E: Failed to update user document in the CouchDB users store. Cause: Couchdb returned a document with an unexpected _id field."), + ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_NUMBER (6206,"GAL6206E: Failed to update user document in the CouchDB users store. Cause: Bad input. User number is invalid or null."), + ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_VERSION(6207,"GAL6207E: Failed to update user document in the CouchDB users store. Cause: Bad input. User document version is invalid or null."), // REST CPS errors ERROR_GALASA_WRONG_NUMBER_OF_PARAMETERS_IN_MESSAGE (6999,"GAL6999E: Failed to render message template. Not the expected number of parameters. Got ''{0}''. Expected ''{1}''"), diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java index 17fcc363..ff27f116 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java @@ -183,6 +183,9 @@ protected List getAllDocsByLoginId(String dbName, String loginId, Strin protected T getDocumentFromDatabase(String dbName, String documentId, Class classOfObject) throws CouchdbException { HttpGet getDocumentRequest = httpRequestFactory.getHttpGetRequest(storeUri + "/" + dbName + "/" + documentId); + + System.out.println("Fetched Document = " + getDocumentRequest); + return gson.fromJson(sendHttpRequest(getDocumentRequest, HttpStatus.SC_OK), classOfObject); } From 7509cc5f30a60f91b5ed660b321798b952a34777 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Mon, 28 Oct 2024 11:33:57 +0000 Subject: [PATCH 3/3] Implemented the changes requested Signed-off-by: Aashir Siddiqui --- .../couchdb/internal/CouchdbAuthStore.java | 72 +++++--- .../internal/CouchdbAuthStoreValidator.java | 2 +- .../auth/couchdb/internal/UserImpl.java | 126 ++++++++++++++ .../internal/beans/FrontEndClient.java | 6 + .../auth/couchdb/internal/beans/UserDoc.java | 81 +++------ .../internal/TestCouchdbAuthStore.java | 16 +- .../TestUserDoc.java => TestUserImpl.java} | 159 ++++++++---------- .../dev/galasa/extensions/common/Errors.java | 2 - .../common/couchdb/CouchdbStore.java | 35 ---- .../mocks/MockFrameworkInitialisation.java | 1 - 10 files changed, 280 insertions(+), 220 deletions(-) create mode 100644 galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/UserImpl.java rename galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/{beans/TestUserDoc.java => TestUserImpl.java} (50%) diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java index 3f31816a..a26768c6 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStore.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.net.URI; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayList; @@ -17,6 +18,8 @@ import org.apache.commons.logging.Log; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; @@ -29,6 +32,7 @@ import dev.galasa.extensions.common.couchdb.CouchdbStore; import dev.galasa.extensions.common.couchdb.CouchdbValidator; import dev.galasa.extensions.common.couchdb.pojos.PutPostResponse; +import dev.galasa.extensions.common.couchdb.pojos.ViewResponse; import dev.galasa.extensions.common.couchdb.pojos.ViewRow; import dev.galasa.extensions.common.api.HttpRequestFactory; import dev.galasa.framework.spi.auth.IInternalAuthToken; @@ -184,7 +188,9 @@ public List getAllUsers() throws AuthStoreException { userDocuments = getAllDocsFromDatabase(USERS_DATABASE_NAME); for (ViewRow row : userDocuments) { - users.add(getUserFromDocument(row.id)); + UserDoc couchdbUserDocBean = getUserFromDocument(row.id); + UserImpl wrappedUser = new UserImpl(couchdbUserDocBean); + users.add(wrappedUser); } logger.info("Users retrieved from CouchDB OK"); @@ -214,7 +220,6 @@ public void createUser(String loginId, String clientName) throws AuthStoreExcept throw new AuthStoreException(errorMessage, e); } - return; } @Override @@ -233,7 +238,6 @@ public IUser getUserByLoginId(String loginId) throws AuthStoreException { // Fetch the user document from the CouchDB using the ID from the row UserDoc fetchedUser = getUserFromDocument(row.id); - logger.info("Fetched user: " + fetchedUser); if (row.value != null) { AuthDBNameViewDesign nameViewDesign = gson.fromJson(gson.toJson(row.value), @@ -242,7 +246,7 @@ public IUser getUserByLoginId(String loginId) throws AuthStoreException { } // Assign fetchedUser to the user variable - user = fetchedUser; + user = new UserImpl(fetchedUser); } logger.info("User retrieved from CouchDB OK"); @@ -259,15 +263,48 @@ public IUser getUserByLoginId(String loginId) throws AuthStoreException { public IUser updateUser(IUser user) throws AuthStoreException { // Take a clone of the user we are passed, so we can guarantee we are using our bean which // serialises to the correct format. - UserDoc userDoc = new UserDoc(user); - updateUserDoc(httpClient, storeUri, 0, userDoc); - return userDoc; + UserImpl userImpl = new UserImpl(user); + updateUser(httpClient, storeUri, userImpl); + return userImpl; } - private void updateUserDoc(CloseableHttpClient httpClient, URI couchdbUri, int attempts, UserDoc user) + /** + * Sends a GET request to CouchDB's + * /{db}/_design/docs/_view/loginId-view?key={loginId} endpoint and returns the + * "rows" list in the response, + * which corresponds to the list of documents within the given database. + * + * @param dbName the name of the database to retrieve the documents of + * @param loginId the loginId of the user to retrieve the doucemnts of + * @return a list of rows corresponding to documents within the database + * @throws CouchdbException if there was a problem accessing the + * CouchDB store or its response + */ + protected List getAllDocsByLoginId(String dbName, String loginId, String viewName) throws CouchdbException { + + String encodedLoginId = URLEncoder.encode("\"" + loginId + "\"", StandardCharsets.UTF_8); + String url = storeUri + "/" + dbName + "/_design/docs/_view/" + viewName + "?key=" + encodedLoginId; + + HttpGet getDocs = httpRequestFactory.getHttpGetRequest(url); + getDocs.addHeader("Content-Type", "application/json"); + + String responseEntity = sendHttpRequest(getDocs, HttpStatus.SC_OK); + + ViewResponse docByLoginId = gson.fromJson(responseEntity, ViewResponse.class); + List viewRows = docByLoginId.rows; + + if (viewRows == null) { + String errorMessage = ERROR_FAILED_TO_GET_DOCUMENTS_FROM_DATABASE.getMessage(dbName); + throw new CouchdbException(errorMessage); + } + + return viewRows; + } + + private void updateUser(CloseableHttpClient httpClient, URI couchdbUri, UserImpl user) throws AuthStoreException { - validateUserDoc(user); + user.validate(); HttpEntityEnclosingRequestBase request = buildUpdateUserDocRequest(user, couchdbUri); @@ -293,10 +330,10 @@ private PutPostResponse sendPutRequestToCouchDb(HttpEntityEnclosingRequestBase r return putResponse; } - private HttpEntityEnclosingRequestBase buildUpdateUserDocRequest(UserDoc user, URI couchdbUri) { - HttpEntityEnclosingRequestBase request; + private HttpPut buildUpdateUserDocRequest(UserImpl user, URI couchdbUri) { + HttpPut request; - String jsonStructure = gson.toJson(user); + String jsonStructure = user.toJson(gson); request = httpRequestFactory .getHttpPutRequest(couchdbUri + "/" + USERS_DATABASE_NAME + "/" + user.getUserNumber()); request.setHeader("If-Match", user.getVersion()); @@ -316,17 +353,6 @@ private void validateCouchdbResponseJson(String expectedUserNumber , PutPostResp } } - private void validateUserDoc(UserDoc user) throws AuthStoreException { - if (user.getUserNumber() == null) { - String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_NUMBER.getMessage(); - throw new AuthStoreException(errorMessage); - } - - if (user.getVersion() == null) { - String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_VERSION.getMessage(); - throw new AuthStoreException(errorMessage); - } - } @Override public void deleteUser(IUser user) throws AuthStoreException { diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java index cbcfd03f..44f2e14e 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/CouchdbAuthStoreValidator.java @@ -133,7 +133,7 @@ private boolean updateDesignDocToDesiredDesignDoc(AuthDBNameViewDesign tableDesi if (tableDesign.views.loginIdView.map == null) { isUpdated = true; - if(dbName.equals("galasa_tokens")){ + if(dbName.equals(DB_TABLE_TOKENS_DESIGN)){ tableDesign.views.loginIdView.map = DB_TABLE_TOKENS_DESIGN; } else{ diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/UserImpl.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/UserImpl.java new file mode 100644 index 00000000..b872c97a --- /dev/null +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/UserImpl.java @@ -0,0 +1,126 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package dev.galasa.auth.couchdb.internal; + +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; +import static dev.galasa.extensions.common.Errors.*; + +import dev.galasa.auth.couchdb.internal.beans.FrontEndClient; +import dev.galasa.auth.couchdb.internal.beans.UserDoc; +import dev.galasa.framework.spi.auth.AuthStoreException; +import dev.galasa.framework.spi.auth.IFrontEndClient; +import dev.galasa.framework.spi.auth.IUser; +import dev.galasa.framework.spi.utils.GalasaGson; + +/** + * A wrapper around the UserDoc bean which gets used when talking to couchdb, + * but one which implements the IUser interface. + */ +public class UserImpl implements IUser { + + private UserDoc userDocBean ; + + UserImpl() { + this.userDocBean = new UserDoc(); + } + + UserImpl(UserDoc userDocBean) { + this.userDocBean = userDocBean ; + } + + public UserImpl(IUser user) { + + // We don't trust that the users' clients which are passed in are going to serialise + // to the correct json format. So convert them into our bean which will. + List trustedClients = new ArrayList(); + for( IFrontEndClient untrustedClient : user.getClients()) { + trustedClients.add( new FrontEndClient(untrustedClient)); + } + + this.userDocBean = new UserDoc( user.getLoginId() , trustedClients); + this.userDocBean.setVersion( user.getVersion() ); + this.userDocBean.setUserNumber( user.getUserNumber() ); + } + + public String toJson( GalasaGson gson) { + return gson.toJson(userDocBean); + } + + @Override + public String getUserNumber() { + return this.userDocBean.getUserNumber(); + } + + public void setUserNumber(String newUserNumber) { + this.userDocBean.setUserNumber(newUserNumber); + } + + @Override + public String getVersion() { + return this.userDocBean.getVersion(); + } + + public void setVersion(String newVersion) { + this.userDocBean.setVersion(newVersion); + } + + @Override + public String getLoginId() { + return this.userDocBean.getLoginId(); + } + + public void setLoginId(String newLoginId) { + this.userDocBean.setLoginId(newLoginId); + } + + @Override + public Collection getClients() { + List results = new ArrayList<>(); + for ( FrontEndClient beanClient : userDocBean.getClients()) { + results.add(beanClient); + } + return results; + } + + @Override + public IFrontEndClient getClient(String clientName) { + IFrontEndClient match = null; + if (clientName != null) { + for (FrontEndClient frontEndClient : userDocBean.getClients()) { + if(clientName.equals(frontEndClient.getClientName())){ + match = frontEndClient; + break; + } + } + } + return match; + } + + @Override + public void addClient(IFrontEndClient clientInput) { + // We don't trust that the interface passed in will serialize to the correct json format that couchdb uses. + // So create our own bean and copy the contents over. + FrontEndClient frontEndClient = new FrontEndClient(clientInput); + userDocBean.getClients().add(frontEndClient); + } + + + public void validate() throws AuthStoreException { + if (getUserNumber() == null) { + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_NUMBER.getMessage(); + throw new AuthStoreException(errorMessage); + } + + if (getVersion() == null) { + String errorMessage = ERROR_FAILED_TO_UPDATE_USER_DOCUMENT_INPUT_INVALID_NULL_USER_VERSION.getMessage(); + throw new AuthStoreException(errorMessage); + } + } + +} \ No newline at end of file diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java index cb3085b2..31d672a1 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/FrontEndClient.java @@ -1,3 +1,9 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + package dev.galasa.auth.couchdb.internal.beans; import com.google.gson.annotations.SerializedName; diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java index c8005052..528e12ae 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/main/java/dev/galasa/auth/couchdb/internal/beans/UserDoc.java @@ -1,16 +1,19 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + package dev.galasa.auth.couchdb.internal.beans; -import java.util.Collection; -import java.util.List; import java.util.ArrayList; - - +import java.util.List; import com.google.gson.annotations.SerializedName; -import dev.galasa.framework.spi.auth.IFrontEndClient; -import dev.galasa.framework.spi.auth.IUser; - -public class UserDoc implements IUser{ +/** + * A document serialised to exchange json with couchdb, representing a user in the system. + */ +public class UserDoc { @SerializedName("_id") private String userNumber; @@ -24,19 +27,15 @@ public class UserDoc implements IUser{ @SerializedName("activity") private List clients; + public UserDoc() { + setClients(new ArrayList()); + } + public UserDoc(String loginId, List clients) { this.loginId = loginId; setClients( clients); } - public UserDoc(IUser user){ - this.loginId = user.getLoginId(); - this.version = user.getVersion(); - this.userNumber = user.getUserNumber(); - setClients(user.getClients()); - } - - @Override public String getUserNumber(){ return userNumber; } @@ -53,7 +52,6 @@ public void setVersion(String version){ this.version = version; } - @Override public String getLoginId() { return loginId; } @@ -61,54 +59,13 @@ public String getLoginId() { public void setLoginId(String loginId) { this.loginId = loginId; } - - @Override - public Collection getClients() { - Collection results = new ArrayList(); - for( FrontEndClient client : this.clients) { - results.add(client); - } - return results; + + public List getClients() { + return clients; } public void setClients(List clients) { - this.clients = new ArrayList(); - if( clients != null) { - for (IFrontEndClient clientIn: clients) { - addClient(clientIn); - } - } - } - - // Setter for clients. Takes a deep copy of any clients it is passed. - public void setClients(Collection clients) { - - this.clients = new ArrayList(); - if( clients != null) { - for (IFrontEndClient clientIn: clients) { - addClient(clientIn); - } - } + this.clients = clients; } - - @Override - public IFrontEndClient getClient(String clientName) { - IFrontEndClient match = null; - if (clientName != null) { - for (FrontEndClient frontEndClient : clients) { - if(clientName.equals(frontEndClient.getClientName())){ - match = frontEndClient; - break; - } - } - } - return match; - } - - @Override - public void addClient(IFrontEndClient client) { - clients.add( new FrontEndClient(client)); - } - } diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java index 00363247..870110c4 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestCouchdbAuthStore.java @@ -623,7 +623,7 @@ public void testGetUserReturnsUsersByLoginIdFromCouchdbOK() throws Exception { // When... IUser user = authStore.getUserByLoginId("user1"); - assertThat(user).isInstanceOf(UserDoc.class); + assertThat(user).isInstanceOf(UserImpl.class); assertThat(user).isNotNull(); assertThat(user.getLoginId()).isEqualTo("johndoe"); } @@ -668,7 +668,7 @@ httpClientFactory, new HttpRequestFactoryImpl(), logFactory, new MockCouchdbVali @Test public void testUpdateUserUpdatesExisitingClientOK() throws Exception { // Given... - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("web-ui", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("web-ui", Instant.MIN)))); mockUser.setVersion("1"); mockUser.setUserNumber("user1"); @@ -686,7 +686,7 @@ public void testUpdateUserUpdatesExisitingClientOK() throws Exception { @Test public void testUpdateUserCouchDBPassesBackANullVersionField() throws Exception { // Given... - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN)))); mockUser.setVersion("1"); mockUser.setUserNumber("user1"); @@ -705,7 +705,7 @@ public void testUpdateUserCouchDBPassesBackANullVersionField() throws Exception @Test public void testUpdateUserCouchDBPassesBackADocIdWithWrongUserNumberField() throws Exception { // Given... - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN)))); mockUser.setVersion("1"); mockUser.setUserNumber("user1"); @@ -725,7 +725,7 @@ public void testUpdateUserCouchDBPassesBackADocIdWithWrongUserNumberField() thro public void testUpdateUserCouchDBPassesBackADocIdWithMissingIdField() throws Exception { // Given... - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN)))); mockUser.setVersion("1"); mockUser.setUserNumber("user1"); @@ -744,7 +744,7 @@ public void testUpdateUserCouchDBPassesBackADocIdWithMissingIdField() throws Exc @Test public void testUpdateUserCouchDBPassesBackAnUnexpectedServerError() throws Exception { // Given... - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN)))); mockUser.setVersion("1"); mockUser.setUserNumber("user1"); @@ -762,7 +762,7 @@ public void testUpdateUserCouchDBPassesBackAnUnexpectedServerError() throws Exce @Test public void testUpdateUserWithBadUserNullIdFieldGetsDetectedAsError() throws Exception { - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN)))); mockUser.setVersion("1"); mockUser.setUserNumber(null); @@ -775,7 +775,7 @@ public void testUpdateUserWithBadUserNullIdFieldGetsDetectedAsError() throws Exc @Test public void testUpdateUserWithBadUserNullVersionFieldGetsDetectedAsError() throws Exception { - UserDoc mockUser = new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN))); + UserImpl mockUser = new UserImpl(new UserDoc("johndoe", List.of(new FrontEndClient("rest-api", Instant.MIN)))); mockUser.setVersion(null); mockUser.setUserNumber("user1"); diff --git a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestUserImpl.java similarity index 50% rename from galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java rename to galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestUserImpl.java index 7276035a..b66bb4a1 100644 --- a/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/beans/TestUserDoc.java +++ b/galasa-extensions-parent/dev.galasa.auth.couchdb/src/test/java/dev/galasa/auth/couchdb/internal/TestUserImpl.java @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package dev.galasa.auth.couchdb.internal.beans; +package dev.galasa.auth.couchdb.internal; import static org.assertj.core.api.Assertions.*; import java.time.Instant; @@ -13,23 +13,37 @@ import org.junit.Test; +import dev.galasa.auth.couchdb.internal.beans.FrontEndClient; import dev.galasa.framework.spi.auth.IFrontEndClient; import dev.galasa.framework.spi.auth.IUser; -public class TestUserDoc { +public class TestUserImpl { @Test - public void testCanConstructUserDocGivenAUserDoc() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId",null); - assertThat(doc1.getLoginId()).isEqualTo("myLoginId"); + public void testCanConstructUserImplGivenAnIUser() throws Exception { + // Given... + UserImpl docInput = new UserImpl(); + docInput.setUserNumber("1234"); + docInput.setLoginId("myLoginId"); + + // When.. + UserImpl docOutput = new UserImpl(docInput); + + // Then... + assertThat(docOutput.getLoginId()).isEqualTo("myLoginId"); + assertThat(docOutput.getUserNumber()).isEqualTo("1234"); } @Test public void testCanConstructUserDocGivenAUserDocWithClient() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); - IFrontEndClient clientGotBack = doc1.getClient("myClient1"); + UserImpl docInput = new UserImpl(); + + docInput.setLoginId("myLoginId"); + docInput.addClient(new FrontEndClient("myClient1",Instant.MIN)); + + IFrontEndClient clientGotBack = docInput.getClient("myClient1"); assertThat(clientGotBack).isNotNull(); assertThat(clientGotBack.getClientName()).isEqualTo("myClient1"); assertThat(clientGotBack.getLastLogin()).isEqualTo(Instant.MIN); @@ -37,17 +51,25 @@ public void testCanConstructUserDocGivenAUserDocWithClient() throws Exception { @Test public void testCanLookForClientWhichIsntInTheUserDocReturnsNull() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + + UserImpl docInput = new UserImpl(); - IFrontEndClient clientGotBack = doc1.getClient("myClient2"); // Client2 isn't there! + docInput.setLoginId("myLoginId"); + docInput.addClient(new FrontEndClient("myClient1",Instant.MIN)); + + IFrontEndClient clientGotBack = docInput.getClient("myClient2"); // Client2 isn't there! assertThat(clientGotBack).isNull(); } @Test public void testCanTryLookingForClientWithNullNameShouldReturnNull() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + + UserImpl docInput = new UserImpl(); + + docInput.setLoginId("myLoginId"); + docInput.addClient(new FrontEndClient("myClient1",Instant.MIN)); - IFrontEndClient clientGotBack = doc1.getClient(null); + IFrontEndClient clientGotBack = docInput.getClient(null); assertThat(clientGotBack).isNull(); } @@ -98,10 +120,13 @@ public String getVersion() { public void testCanCloneUserDocFromIUser() throws Exception { MockIUser doc1 = new MockIUser("myLoginId",null); - UserDoc doc2 = new UserDoc(doc1); + UserImpl docInput = new UserImpl(); - assertThat(doc2).isNotNull(); - assertThat(doc2.getLoginId()).isEqualTo("myLoginId"); + docInput.setLoginId("myLoginId"); + docInput.addClient(new FrontEndClient("myClient1",Instant.MIN)); + + assertThat(docInput).isNotNull(); + assertThat(docInput.getLoginId()).isEqualTo("myLoginId"); } class MockIFrontEndClient implements IFrontEndClient { @@ -132,26 +157,31 @@ public void setLastLogin(Instant lastLoginTime) { @Test public void testCanSetIFrontEndClientsInAndGetThemBack() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId",List.of(new FrontEndClient("myClient1",Instant.MIN))); + MockIFrontEndClient mockClient = new MockIFrontEndClient("client2", Instant.MIN.plusSeconds(2)); - doc1.addClient(mockClient); + UserImpl docInput = new UserImpl(); + docInput.setLoginId("myLoginId"); + docInput.addClient(mockClient); // Then... - IFrontEndClient gotBack = doc1.getClient("client2"); + IFrontEndClient gotBack = docInput.getClient("client2"); assertThat(gotBack).isNotNull(); assertThat(gotBack.getLastLogin()).isEqualTo(Instant.MIN.plusSeconds(2)); } @Test public void testCanGetClientsWhenClientsArePresent() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of( - new FrontEndClient("client1", Instant.MIN), - new FrontEndClient("client2", Instant.now()) - )); + MockIFrontEndClient mockClient1 = new MockIFrontEndClient("client1", Instant.MIN.plusSeconds(2)); + MockIFrontEndClient mockClient2 = new MockIFrontEndClient("client2", Instant.MIN.plusSeconds(2)); + + UserImpl docInput = new UserImpl(); + docInput.setLoginId("myLoginId"); + docInput.addClient(mockClient1); + docInput.addClient(mockClient2); - Collection clients = doc1.getClients(); + Collection clients = docInput.getClients(); assertThat(clients).isNotNull(); assertThat(clients).hasSize(2); @@ -160,9 +190,10 @@ public void testCanGetClientsWhenClientsArePresent() throws Exception { @Test public void testCanGetClientsWhenNoClientsArePresent() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); + + UserImpl docInput = new UserImpl(); - Collection clients = doc1.getClients(); + Collection clients = docInput.getClients(); assertThat(clients).isNotNull(); assertThat(clients).isEmpty(); @@ -170,10 +201,10 @@ public void testCanGetClientsWhenNoClientsArePresent() throws Exception { @Test public void testCanSetAndGetUserNumber() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - doc1.setUserNumber("12345"); + UserImpl docInput = new UserImpl(); + docInput.setUserNumber("12345"); - String userNumber = doc1.getUserNumber(); + String userNumber = docInput.getUserNumber(); assertThat(userNumber).isNotNull(); assertThat(userNumber).isEqualTo("12345"); @@ -181,19 +212,18 @@ public void testCanSetAndGetUserNumber() throws Exception { @Test public void testGetUserNumberReturnsNullWhenNotSet() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - - String userNumber = doc1.getUserNumber(); + UserImpl docInput = new UserImpl(); + String userNumber = docInput.getUserNumber(); assertThat(userNumber).isNull(); } @Test public void testCanSetAndGetVersion() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - doc1.setVersion("1.0"); + UserImpl docInput = new UserImpl(); + docInput.setVersion("1.0"); - String version = doc1.getVersion(); + String version = docInput.getVersion(); assertThat(version).isNotNull(); assertThat(version).isEqualTo("1.0"); @@ -201,19 +231,19 @@ public void testCanSetAndGetVersion() throws Exception { @Test public void testGetVersionReturnsNullWhenNotSet() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - - String version = doc1.getVersion(); + + UserImpl docInput = new UserImpl(); + String version = docInput.getVersion(); assertThat(version).isNull(); } @Test public void testCanSetAndGetLoginId() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - doc1.setLoginId("newLoginId"); + UserImpl docInput = new UserImpl(); + docInput.setLoginId("newLoginId"); - String loginId = doc1.getLoginId(); + String loginId = docInput.getLoginId(); assertThat(loginId).isNotNull(); assertThat(loginId).isEqualTo("newLoginId"); @@ -221,58 +251,11 @@ public void testCanSetAndGetLoginId() throws Exception { @Test public void testGetLoginIdReturnsNullWhenNotSet() throws Exception { - UserDoc doc1 = new UserDoc(null, List.of()); + UserImpl docInput = new UserImpl(); - String loginId = doc1.getLoginId(); + String loginId = docInput.getLoginId(); assertThat(loginId).isNull(); } - @Test - public void testCanSetClientsSuccessfully() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - Collection clients = List.of( - new FrontEndClient("client1", Instant.MIN), - new FrontEndClient("client2", Instant.now()) - ); - - doc1.setClients(clients); - - Collection clientsGotBack = doc1.getClients(); - assertThat(clientsGotBack).isNotNull(); - assertThat(clientsGotBack).hasSize(2); - assertThat(clientsGotBack).extracting("clientName").containsExactlyInAnyOrder("client1", "client2"); - } - - @Test - public void testSetClientsDoesNotModifyOriginalCollection() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - List originalClients = new ArrayList<>(); - originalClients.add(new FrontEndClient("client1", Instant.MIN)); - - doc1.setClients(originalClients); - - // Modify the original collection after setting it - originalClients.add(new FrontEndClient("client2", Instant.now())); - - Collection clientsGotBack = doc1.getClients(); - assertThat(clientsGotBack).hasSize(1); // Should only contain the first client - assertThat(clientsGotBack).extracting("clientName").containsExactly("client1"); - } - - @Test - public void testSetClientsShouldInvokeAddClientForEachClient() throws Exception { - UserDoc doc1 = new UserDoc("myLoginId", List.of()); - Collection clients = List.of( - new FrontEndClient("client1", Instant.MIN), - new FrontEndClient("client2", Instant.now()) - ); - - doc1.setClients(clients); - - Collection clientsGotBack = doc1.getClients(); - assertThat(clientsGotBack).hasSize(2); - assertThat(clientsGotBack).extracting("clientName").contains("client1", "client2"); - } - } diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java index 85803443..e3898224 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/Errors.java @@ -35,8 +35,6 @@ public enum Errors { ERROR_FAILED_TO_INITIALISE_AUTH_STORE (6103,"GAL6103E: Failed to initialise the Galasa CouchDB auth store. Cause: {0}"), ERROR_FAILED_TO_DELETE_TOKEN_DOCUMENT (6104,"GAL6104E: Failed to delete auth token from the CouchDB tokens database. Cause: {0}"), - // CouchDB Users Store error - ERROR_FAILED_TO_INITIALISE_USERS_STORE (6200,"GAL6200E: Failed to initialise the Galasa CouchDB users store. Cause: {0}"), ERROR_FAILED_TO_CREATE_USER_DOCUMENT (6201,"GAL6201E: Failed to store users token in the CouchDB users database. Cause: {0}"), ERROR_FAILED_TO_RETRIEVE_USERS (6202,"GAL6202E: Failed to get user documents from the CouchDB users store. Cause: {0}"), ERROR_FAILED_TO_UPDATE_USER_DOCUMENT (6203,"GAL6203E: Failed to update user document in the CouchDB users store. Cause: {0}"), diff --git a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java index ff27f116..104ba14a 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java +++ b/galasa-extensions-parent/dev.galasa.extensions.common/src/main/java/dev/galasa/extensions/common/couchdb/CouchdbStore.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.CopyOption; import java.nio.file.Files; @@ -133,38 +132,6 @@ protected List getAllDocsFromDatabase(String dbName) throws CouchdbExce return viewRows; } - /** - * Sends a GET request to CouchDB's - * /{db}/_design/docs/_view/loginId-view?key={loginId} endpoint and returns the - * "rows" list in the response, - * which corresponds to the list of documents within the given database. - * - * @param dbName the name of the database to retrieve the documents of - * @param loginId the loginId of the user to retrieve the doucemnts of - * @return a list of rows corresponding to documents within the database - * @throws CouchdbException if there was a problem accessing the - * CouchDB store or its response - */ - protected List getAllDocsByLoginId(String dbName, String loginId, String viewName) throws CouchdbException { - - String encodedLoginId = URLEncoder.encode("\"" + loginId + "\"", StandardCharsets.UTF_8); - String url = storeUri + "/" + dbName + "/_design/docs/_view/" + viewName + "?key=" + encodedLoginId; - - HttpGet getDocs = httpRequestFactory.getHttpGetRequest(url); - getDocs.addHeader("Content-Type", "application/json"); - - String responseEntity = sendHttpRequest(getDocs, HttpStatus.SC_OK); - - ViewResponse docByLoginId = gson.fromJson(responseEntity, ViewResponse.class); - List viewRows = docByLoginId.rows; - - if (viewRows == null) { - String errorMessage = ERROR_FAILED_TO_GET_DOCUMENTS_FROM_DATABASE.getMessage(dbName); - throw new CouchdbException(errorMessage); - } - - return viewRows; - } /** * Gets an object from a given database's document using its document ID by @@ -184,8 +151,6 @@ protected T getDocumentFromDatabase(String dbName, String documentId, Class< throws CouchdbException { HttpGet getDocumentRequest = httpRequestFactory.getHttpGetRequest(storeUri + "/" + dbName + "/" + documentId); - System.out.println("Fetched Document = " + getDocumentRequest); - return gson.fromJson(sendHttpRequest(getDocumentRequest, HttpStatus.SC_OK), classOfObject); } diff --git a/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java b/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java index 699f7185..bedd2cb5 100644 --- a/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java +++ b/galasa-extensions-parent/dev.galasa.extensions.mocks/src/main/java/dev/galasa/extensions/mocks/MockFrameworkInitialisation.java @@ -33,7 +33,6 @@ public class MockFrameworkInitialisation implements IApiServerInitialisation { private MockFramework framework; protected URI authStoreUri; - protected URI usersStoreUri; protected URI cpsBootstrapUri; private List registeredAuthStores = new ArrayList();