diff --git a/.env b/.env index 6d2059a9..bc724350 100644 --- a/.env +++ b/.env @@ -32,7 +32,7 @@ NEAR_CREDENTIALS_DIR=.near-credentials NEAR_CONTRACT_NAME=sputnikv2.testnet NEAR_TOKEN_FACTORY_CONTRACT_NAME=tokenfactory.testnet NEAR_BRIDGE_TOKEN_FACTORY_CONTRACT_NAME=f.ropsten.testnet -NEAR_TOKEN_API_URL=https://www.sodaki.com/api +NEAR_TOKEN_API_URL=https://sodaki.com/api AGGREGATOR_POLLING_INTERVAL=2000 AGGREGATOR_TOKEN_POLLING_INTERVAL=7000 diff --git a/.github/env.develop b/.github/env.develop index f3daf6ef..cf35ec4e 100644 --- a/.github/env.develop +++ b/.github/env.develop @@ -7,7 +7,7 @@ K8S_INGRESS_HOST=api.dev.app.astrodao.com WALLET_CALLBACK_URL=https://dev.app.astrodao.com/callback/transaction NEAR_BRIDGE_TOKEN_FACTORY_CONTRACT_NAME=f.ropsten.testnet TEST_ENV_NAME=dev -NEAR_TOKEN_API_URL=https://www.sodaki.com/api +NEAR_TOKEN_API_URL=https://sodaki.com/api AGGREGATOR_TOKEN_PRICES_POLLING_INTERVAL=100000 # 12 hours AGGREGATOR_DAO_STATUS_POLLING_INTERVAL=43200000 diff --git a/.github/env.production b/.github/env.production index d621a6cb..5cb6406d 100644 --- a/.github/env.production +++ b/.github/env.production @@ -7,7 +7,7 @@ K8S_INGRESS_HOST=api.app.astrodao.com WALLET_CALLBACK_URL=https://app.astrodao.com/callback/transaction NEAR_BRIDGE_TOKEN_FACTORY_CONTRACT_NAME=factory.bridge.near NEAR_NFT_WHITELIST_CONTRACTS=starpause.mintbase1.near -NEAR_TOKEN_API_URL=https://www.sodaki.com/api +NEAR_TOKEN_API_URL=https://sodaki.com/api AGGREGATOR_TOKEN_PRICES_POLLING_INTERVAL=100000 # 12 hours AGGREGATOR_DAO_STATUS_POLLING_INTERVAL=43200000 diff --git a/.github/env.staging b/.github/env.staging index bfb79f3a..5ac266c8 100644 --- a/.github/env.staging +++ b/.github/env.staging @@ -7,7 +7,7 @@ K8S_INGRESS_HOST=api.staging.app.astrodao.com WALLET_CALLBACK_URL=https://staging.app.astrodao.com/callback/transaction NEAR_BRIDGE_TOKEN_FACTORY_CONTRACT_NAME=factory.bridge.near NEAR_NFT_WHITELIST_CONTRACTS=starpause.mintbase1.near -NEAR_TOKEN_API_URL=https://www.sodaki.com/api +NEAR_TOKEN_API_URL=https://sodaki.com/api AGGREGATOR_TOKEN_PRICES_POLLING_INTERVAL=100000 # 12 hours AGGREGATOR_DAO_STATUS_POLLING_INTERVAL=43200000 diff --git a/.github/env.test b/.github/env.test index 9e068504..ca950e2b 100644 --- a/.github/env.test +++ b/.github/env.test @@ -7,7 +7,7 @@ K8S_INGRESS_HOST=api.testnet.app.astrodao.com WALLET_CALLBACK_URL=https://testnet.app.astrodao.com/callback/transaction NEAR_BRIDGE_TOKEN_FACTORY_CONTRACT_NAME=f.ropsten.testnet TEST_ENV_NAME=testnet -NEAR_TOKEN_API_URL=https://www.sodaki.com/api +NEAR_TOKEN_API_URL=https://sodaki.com/api AGGREGATOR_TOKEN_PRICES_POLLING_INTERVAL=100000 # 12 hours AGGREGATOR_DAO_STATUS_POLLING_INTERVAL=43200000 diff --git a/README.md b/README.md index bc83aa13..46941bc7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Astro API Gateway +[![Release version](https://img.shields.io/github/v/release/near-daos/astro-api-gateway)](https://github.com/near-daos/astro-api-gateway/releases/) +[![Build](https://github.com/near-daos/astro-api-gateway/actions/workflows/build-deploy.yaml/badge.svg)](https://github.com/near-daos/astro-api-gateway/actions/workflows/build-deploy.yaml) +[![Launch API Autotests On Schedule](https://github.com/near-daos/astro-api-gateway/actions/workflows/launch-autotests-on-schedule.yaml/badge.svg)](https://github.com/near-daos/astro-api-gateway/actions/workflows/launch-autotests-on-schedule.yaml) +[![Telegram group](https://img.shields.io/badge/-Telegram%20group-blue)](https://t.me/astro_near) + Astro API Gateway contains list of services used by DAO applications based on [Sputnik DAO Contracts](https://github.com/near-daos/sputnik-dao-contract) version 2 on [NEAR Protocol](https://near.org/). Main features: diff --git a/apps/aggregator/deployment/app-chart/values.yaml b/apps/aggregator/deployment/app-chart/values.yaml index 5ba4400a..ff2dda12 100644 --- a/apps/aggregator/deployment/app-chart/values.yaml +++ b/apps/aggregator/deployment/app-chart/values.yaml @@ -102,4 +102,4 @@ environment: # Every day at 23:55 aggregator_dao_stats_cron_time: "55 23 * * *" wallet_callback_url: "" - near_token_api_url: "https://www.sodaki.com/api" + near_token_api_url: "https://sodaki.com/api" diff --git a/test-framework/README.md b/test-framework/README.md index b496f5de..1bf65275 100644 --- a/test-framework/README.md +++ b/test-framework/README.md @@ -4,7 +4,7 @@ This framework is intended to be used for testing of Sputnik v2 API [![Launch API Autotests On Schedule](https://github.com/near-daos/astro-api-gateway/actions/workflows/launch-autotests-on-schedule.yaml/badge.svg)](https://github.com/near-daos/astro-api-gateway/actions/workflows/launch-autotests-on-schedule.yaml) [![Manual API Autotests run](https://github.com/near-daos/astro-api-gateway/actions/workflows/run-autotests.yaml/badge.svg)](https://github.com/near-daos/astro-api-gateway/actions/workflows/run-autotests.yaml) -##Test execution reports +## Test execution reports - [API test coverage and Allure reports - **develop** environment](https://automation-report.app.astrodao.com/develop/) - [API test coverage and Allure reports - **testnet** environment](https://automation-report.app.astrodao.com/test/) diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/controllers/SubscriptionsApi.java b/test-framework/src/main/java/api/app/astrodao/com/core/controllers/SubscriptionsApi.java new file mode 100644 index 00000000..d6e27313 --- /dev/null +++ b/test-framework/src/main/java/api/app/astrodao/com/core/controllers/SubscriptionsApi.java @@ -0,0 +1,70 @@ +package api.app.astrodao.com.core.controllers; + +import api.app.astrodao.com.core.clients.HttpClient; +import api.app.astrodao.com.core.utils.JsonUtils; +import api.app.astrodao.com.openapi.models.SubscriptionDeleteDto; +import api.app.astrodao.com.openapi.models.SubscriptionDto; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.Collections; + +@Component +@RequiredArgsConstructor +public class SubscriptionsApi { + protected final HttpClient httpClient; + + @Value("${framework.api.url}") + private String apiUrl; + + public ResponseEntity subscribeDao(String accountId, String publicKey, String signature, String daoId) { + HttpHeaders httpHeaders = httpClient.getBasicHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.setAccept(Collections.singletonList(MediaType.ALL)); + + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(apiUrl); + builder.pathSegment("subscriptions"); + + SubscriptionDto subscriptionDto = new SubscriptionDto(); + subscriptionDto.setAccountId(accountId); + subscriptionDto.setPublicKey(publicKey); + subscriptionDto.setSignature(signature); + subscriptionDto.setDaoId(daoId); + + HttpEntity httpEntity = new HttpEntity<>(JsonUtils.writeValueAsString(subscriptionDto), httpHeaders); + return httpClient.post(builder.toUriString(), httpEntity, String.class); + } + + public ResponseEntity accountSubscriptions(String accountId) { + HttpHeaders httpHeaders = httpClient.getBasicHeaders(); + httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(apiUrl); + builder.pathSegment("subscriptions", "account-subscriptions", "{accountId}"); + + return httpClient.get(builder.build().toString(), new HttpEntity<>(httpHeaders), String.class, accountId); + } + + public ResponseEntity deleteSubscription(String accountId, String publicKey, String signature, String daoId) { + HttpHeaders httpHeaders = httpClient.getBasicHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.setAccept(Collections.singletonList(MediaType.ALL)); + + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(apiUrl); + builder.pathSegment("subscriptions", daoId); + + SubscriptionDeleteDto subscriptionDto = new SubscriptionDeleteDto(); + subscriptionDto.setAccountId(accountId); + subscriptionDto.setPublicKey(publicKey); + subscriptionDto.setSignature(signature); + + HttpEntity httpEntity = new HttpEntity<>(JsonUtils.writeValueAsString(subscriptionDto), httpHeaders); + return httpClient.delete(builder.toUriString(), httpEntity, String.class); + } +} diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/controllers/TransactionsApi.java b/test-framework/src/main/java/api/app/astrodao/com/core/controllers/TransactionsApi.java index ef246265..3a851ad9 100644 --- a/test-framework/src/main/java/api/app/astrodao/com/core/controllers/TransactionsApi.java +++ b/test-framework/src/main/java/api/app/astrodao/com/core/controllers/TransactionsApi.java @@ -20,9 +20,6 @@ public class TransactionsApi { @Value("${framework.api.url}") private String apiUrl; - @Value("${framework.app.url}") - private String appUrl; - public ResponseEntity triggerCallback(String accountId, String transactionHashes) { HttpHeaders httpHeaders = httpClient.getBasicHeaders(); httpHeaders.setAccept(Collections.singletonList(MediaType.ALL)); diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/dto/api/subscription/Subscriptions.java b/test-framework/src/main/java/api/app/astrodao/com/core/dto/api/subscription/Subscriptions.java new file mode 100644 index 00000000..9ba25ea6 --- /dev/null +++ b/test-framework/src/main/java/api/app/astrodao/com/core/dto/api/subscription/Subscriptions.java @@ -0,0 +1,8 @@ +package api.app.astrodao.com.core.dto.api.subscription; + +import api.app.astrodao.com.openapi.models.Subscription; + +import java.util.ArrayList; + +public class Subscriptions extends ArrayList { +} diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/exceptions/EntityNotFoundException.java b/test-framework/src/main/java/api/app/astrodao/com/core/exceptions/EntityNotFoundException.java new file mode 100644 index 00000000..63de702a --- /dev/null +++ b/test-framework/src/main/java/api/app/astrodao/com/core/exceptions/EntityNotFoundException.java @@ -0,0 +1,7 @@ +package api.app.astrodao.com.core.exceptions; + +public class EntityNotFoundException extends RuntimeException { + public EntityNotFoundException(String message) { + super(message); + } +} diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/exceptions/NotEnoughBalanceExecution.java b/test-framework/src/main/java/api/app/astrodao/com/core/exceptions/NotEnoughBalanceExecution.java new file mode 100644 index 00000000..ea500935 --- /dev/null +++ b/test-framework/src/main/java/api/app/astrodao/com/core/exceptions/NotEnoughBalanceExecution.java @@ -0,0 +1,7 @@ +package api.app.astrodao.com.core.exceptions; + +public class NotEnoughBalanceExecution extends RuntimeException { + public NotEnoughBalanceExecution(String message) { + super(message); + } +} diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/interceptor/SwaggerCoverageV3RestTemplate.java b/test-framework/src/main/java/api/app/astrodao/com/core/interceptor/SwaggerCoverageV3RestTemplate.java index 39ff6135..467ea29a 100644 --- a/test-framework/src/main/java/api/app/astrodao/com/core/interceptor/SwaggerCoverageV3RestTemplate.java +++ b/test-framework/src/main/java/api/app/astrodao/com/core/interceptor/SwaggerCoverageV3RestTemplate.java @@ -10,6 +10,7 @@ import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.HeaderParameter; import io.swagger.v3.oas.models.parameters.QueryParameter; +import io.swagger.v3.oas.models.parameters.RequestBody; import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.servers.Server; @@ -72,14 +73,24 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp .collect(Collectors.toMap(s -> s[0], s -> s[1])) .forEach((n, v) -> mediaType.getSchema().addProperties(n, new Schema().example(v))); } + operation.requestBody(new RequestBody().content( + new Content().addMediaType(request.getHeaders().getContentType().toString(), mediaType)) + ); } ClientHttpResponse response = execution.execute(request, body); - operation.responses(new ApiResponses().addApiResponse(valueOf(response.getRawStatusCode()), - new ApiResponse().content( - new Content().addMediaType(response.getHeaders().getContentType().toString(), new MediaType()))) - ); + if (response.getHeaders().getContentType() != null) { + operation.responses(new ApiResponses().addApiResponse(valueOf(response.getRawStatusCode()), + new ApiResponse().content( + new Content().addMediaType(response.getHeaders().getContentType().toString(), new MediaType()))) + ); + } else { + operation.responses(new ApiResponses().addApiResponse(valueOf(response.getRawStatusCode()), + new ApiResponse().content( + new Content().addMediaType(ApiResponses.DEFAULT, new MediaType()))) + ); + } PathItem pathItem = new PathItem(); pathItem.operation(PathItem.HttpMethod.valueOf(request.getMethod().name()), operation); diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/utils/CLIUtils.java b/test-framework/src/main/java/api/app/astrodao/com/core/utils/CLIUtils.java index a87fd965..564a37c0 100644 --- a/test-framework/src/main/java/api/app/astrodao/com/core/utils/CLIUtils.java +++ b/test-framework/src/main/java/api/app/astrodao/com/core/utils/CLIUtils.java @@ -2,6 +2,7 @@ import api.app.astrodao.com.core.exceptions.CLIExecutionNotSuccessful; import api.app.astrodao.com.core.exceptions.FailedToExecuteCLICommand; +import api.app.astrodao.com.core.exceptions.NotEnoughBalanceExecution; import io.qameta.allure.Allure; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; @@ -39,11 +40,19 @@ public synchronized static List execute(String command) { output.stream().collect(Collectors.joining(System.lineSeparator()))); int exitValue = process.exitValue(); + String outputText = output.stream().collect(Collectors.joining(System.lineSeparator())); if (exitValue != 0) { - throw new CLIExecutionNotSuccessful( - String.format("Something went wrong, got '%s' exit value for the process, console output:\n%s", - exitValue, output.stream().collect(Collectors.joining(System.lineSeparator()))) - ); + if (outputText.contains("not enough balance")) { + throw new NotEnoughBalanceExecution( + String.format("Looks like you don't have enough balance for the account, console output:\n%s", + outputText) + ); + } else { + throw new CLIExecutionNotSuccessful( + String.format("Something went wrong, got '%s' exit value for the process, console output:\n%s", + exitValue, outputText) + ); + } } return output; diff --git a/test-framework/src/main/java/api/app/astrodao/com/core/utils/JsonUtils.java b/test-framework/src/main/java/api/app/astrodao/com/core/utils/JsonUtils.java index 78a77e34..e23a0a2a 100644 --- a/test-framework/src/main/java/api/app/astrodao/com/core/utils/JsonUtils.java +++ b/test-framework/src/main/java/api/app/astrodao/com/core/utils/JsonUtils.java @@ -17,7 +17,7 @@ @UtilityClass public class JsonUtils { - private static final ObjectMapper MAPPER; + public static final ObjectMapper MAPPER; static { MAPPER = new ObjectMapper(); diff --git a/test-framework/src/main/resources/configs/framework.yml b/test-framework/src/main/resources/configs/framework.yml index 00b27cda..9a7322a3 100644 --- a/test-framework/src/main/resources/configs/framework.yml +++ b/test-framework/src/main/resources/configs/framework.yml @@ -6,4 +6,10 @@ test: aggregation.timeout: ${TEST_AGG_TIMEOUT:20} framework: - http.timeout: 20 \ No newline at end of file + http.timeout: 20 + +accounts: + account1: + accountId: testdao2.testnet + publicKey: ed25519:8kqG6dyAheDqzoZh4hk9HbjBngG6fTnCD4zTw6sVud9K + signature: JJoq7fvBh6JXH9kVXtV0SoeT3YDSm9Gr6Ug4cmJNXAtuU2bZFu9/8bN26xw7MwYBRdWTZr2v81JZDO9zYaE8Dg== \ No newline at end of file diff --git a/test-framework/src/test/java/api/app/astrodao/com/steps/BaseSteps.java b/test-framework/src/test/java/api/app/astrodao/com/steps/BaseSteps.java index 50699b38..58e41c35 100644 --- a/test-framework/src/test/java/api/app/astrodao/com/steps/BaseSteps.java +++ b/test-framework/src/test/java/api/app/astrodao/com/steps/BaseSteps.java @@ -98,6 +98,13 @@ public void assertDtoValueIsNull(T dto, Function valueExtractor, St .isNull(); } + @Step("User sees string contains value") + public void assertStringContainsValue(String value, String expectedValue) { + assertThat(value) + .as("String should contain value.") + .contains(expectedValue); + } + public T getResponseDto(ResponseEntity entity, Class clazz) { return JsonUtils.readValue(entity.getBody(), clazz); } diff --git a/test-framework/src/test/java/api/app/astrodao/com/steps/SubscriptionsApiSteps.java b/test-framework/src/test/java/api/app/astrodao/com/steps/SubscriptionsApiSteps.java new file mode 100644 index 00000000..c4565960 --- /dev/null +++ b/test-framework/src/test/java/api/app/astrodao/com/steps/SubscriptionsApiSteps.java @@ -0,0 +1,60 @@ +package api.app.astrodao.com.steps; + +import api.app.astrodao.com.core.annotations.Steps; +import api.app.astrodao.com.core.controllers.SubscriptionsApi; +import api.app.astrodao.com.core.dto.api.subscription.Subscriptions; +import api.app.astrodao.com.core.exceptions.EntityNotFoundException; +import api.app.astrodao.com.openapi.models.Subscription; +import io.qameta.allure.Step; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Objects; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@Steps +@RequiredArgsConstructor +public class SubscriptionsApiSteps extends BaseSteps { + private final SubscriptionsApi subscriptionsApi; + + public void cleanUpSubscriptions(String accountId, String publicKey, String signature) { + ResponseEntity response = subscriptionsApi.accountSubscriptions(accountId); + assertResponseStatusCode(response, HttpStatus.OK); + Subscriptions subscriptions = getResponseDto(response, Subscriptions.class); + subscriptions.forEach(p -> { + ResponseEntity resp = subscriptionsApi.deleteSubscription(accountId, publicKey, signature, p.getId()); + assertResponseStatusCode(resp, HttpStatus.OK); + }); + } + + @Step("User subscribes to DAO") + public ResponseEntity subscribeDao(String accountId, String publicKey, String signature, String daoId) { + return subscriptionsApi.subscribeDao(accountId, publicKey, signature, daoId); + } + + @Step("User get subscriptions") + public ResponseEntity accountSubscriptions(String accountId) { + return subscriptionsApi.accountSubscriptions(accountId); + } + + @Step("User deletes subscription") + public ResponseEntity deleteSubscription(String accountId, String publicKey, String signature, String daoId) { + return subscriptionsApi.deleteSubscription(accountId, publicKey, signature, daoId); + } + + public Subscription getCreatedSubscription(Subscriptions subscriptions, String subscriptionId) { + return subscriptions.stream() + .filter(p -> Objects.equals(p.getId(), subscriptionId)).findFirst() + .orElseThrow(() -> new EntityNotFoundException("Subscription not found")); + } + + @Step("User sees subscription is not present in a list") + public void verifySubscriptionHasBeenDeleted(Subscriptions subscriptions, String subscriptionId) { + assertThat(subscriptions) + .as(String.format("'%s' subscription should not be present in collection.", subscriptionId)) + .map(Subscription::getId) + .doesNotContain(subscriptionId); + } +} diff --git a/test-framework/src/test/java/api/app/astrodao/com/tests/SubscriptionApiTests.java b/test-framework/src/test/java/api/app/astrodao/com/tests/SubscriptionApiTests.java new file mode 100644 index 00000000..a100a61f --- /dev/null +++ b/test-framework/src/test/java/api/app/astrodao/com/tests/SubscriptionApiTests.java @@ -0,0 +1,169 @@ +package api.app.astrodao.com.tests; + +import api.app.astrodao.com.core.dto.api.subscription.Subscriptions; +import api.app.astrodao.com.openapi.models.Subscription; +import api.app.astrodao.com.steps.SubscriptionsApiSteps; +import com.github.javafaker.Faker; +import io.qameta.allure.Feature; +import io.qameta.allure.Severity; +import io.qameta.allure.SeverityLevel; +import io.qameta.allure.Story; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@Tags({@Tag("all"), @Tag("subscriptionApiTests")}) +@Feature("SUBSCRIPTION API TESTS") +@DisplayName("SUBSCRIPTION API TESTS") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class SubscriptionApiTests extends BaseTest { + private final SubscriptionsApiSteps subscriptionsApiSteps; + private final Faker faker; + + @Value("${accounts.account1.accountId}") + private String accountId; + + @Value("${accounts.account1.publicKey}") + private String accountPublicKey; + + @Value("${accounts.account1.signature}") + private String accountSignature; + + @BeforeAll + public void cleanUpSubscriptions() { + subscriptionsApiSteps.cleanUpSubscriptions(accountId, accountPublicKey, accountSignature); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should be able to subscribe to a DAO") + void userShouldBeAbleToSubscribeToADao() { + String dao = "marmaj.sputnikv2.testnet"; + String subscriptionId = String.format("%s-%s", dao, accountId); + ResponseEntity response = subscriptionsApiSteps.subscribeDao(accountId, accountPublicKey, accountSignature, dao); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.CREATED); + + Subscription subscription = subscriptionsApiSteps.getResponseDto(response, Subscription.class); + subscriptionsApiSteps.assertDtoValue(subscription, Subscription::getId, subscriptionId, "id"); + subscriptionsApiSteps.assertDtoValue(subscription, Subscription::getAccountId, accountId, "accountId"); + subscriptionsApiSteps.assertDtoValue(subscription, Subscription::getDaoId, dao, "daoId"); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should be able to get subscriptions for account") + void userShouldBeAbleToGetSubscriptionsForAccount() { + String dao = "spacex.sputnikv2.testnet"; + String subscriptionId = String.format("%s-%s", dao, accountId); + + ResponseEntity response = subscriptionsApiSteps.accountSubscriptions(accountId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.OK); + + Subscriptions subscriptionsBefore = subscriptionsApiSteps.getResponseDto(response, Subscriptions.class); + + response = subscriptionsApiSteps.subscribeDao(accountId, accountPublicKey, accountSignature, dao); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.CREATED); + + response = subscriptionsApiSteps.accountSubscriptions(accountId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.OK); + + Subscriptions subscriptionsAfter = subscriptionsApiSteps.getResponseDto(response, Subscriptions.class); + subscriptionsApiSteps.assertCollectionHasCorrectSize(subscriptionsAfter, subscriptionsBefore.size() + 1); + + Subscription createdSubscription = subscriptionsApiSteps.getCreatedSubscription(subscriptionsAfter, subscriptionId); + subscriptionsApiSteps.assertDtoValue(createdSubscription, Subscription::getId, subscriptionId, "id"); + subscriptionsApiSteps.assertDtoValue(createdSubscription, Subscription::getAccountId, accountId, "accountId"); + subscriptionsApiSteps.assertDtoValue(createdSubscription, Subscription::getDaoId, dao, "daoId"); + subscriptionsApiSteps.assertDtoValue(createdSubscription, p -> p.getDao().getId(), dao, "daoId"); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should be able to delete a subscription for account") + void userShouldBeAbleToDeleteSubscriptionForAccount() { + String dao = "autotest-dao-1.sputnikv2.testnet"; + String subscriptionId = String.format("%s-%s", dao, accountId); + + ResponseEntity response = subscriptionsApiSteps.subscribeDao(accountId, accountPublicKey, accountSignature, dao); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.CREATED); + + Subscription subscription = subscriptionsApiSteps.getResponseDto(response, Subscription.class); + subscriptionsApiSteps.assertDtoValue(subscription, Subscription::getId, subscriptionId, "id"); + + response = subscriptionsApiSteps.deleteSubscription(accountId, accountPublicKey, accountSignature, subscriptionId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.OK); + + response = subscriptionsApiSteps.accountSubscriptions(accountId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.OK); + + Subscriptions subscriptions = subscriptionsApiSteps.getResponseDto(response, Subscriptions.class); + subscriptionsApiSteps.verifySubscriptionHasBeenDeleted(subscriptions, subscriptionId); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should not be able to delete invalid subscription for account") + void userShouldNotBeAbleToDeleteInvalidSubscription() { + String dao = "autotest.sputnikv2.testnet"; + String subscriptionId = String.format("%s-%s", dao, accountId); + String expectedResponse = String.format("Subscription with id %s not found", subscriptionId); + + ResponseEntity response = subscriptionsApiSteps.deleteSubscription(accountId, accountPublicKey, accountSignature, subscriptionId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.NOT_FOUND); + subscriptionsApiSteps.assertStringContainsValue(response.getBody(), expectedResponse); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should not be able to delete subscription for account with invalid signature") + void userShouldNotBeAbleToDeleteSubscriptionWithInvalidSignature() { + String dao = "autotest.sputnikv2.testnet"; + String subscriptionId = String.format("%s-%s", dao, accountId); + String expectedResponse = "Invalid signature"; + String invalidSignature = faker.lorem().characters(12, 24); + + ResponseEntity response = subscriptionsApiSteps.deleteSubscription(accountId, accountPublicKey, invalidSignature, subscriptionId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.FORBIDDEN); + subscriptionsApiSteps.assertStringContainsValue(response.getBody(), expectedResponse); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should be able to subscribe to an invalid DAO") + void userShouldNotBeAbleToSubscribeToInvalidDao() { + String dao = "ewqeerdel.sputnikv2.testnet"; + String expectedResponse = String.format("No DAO '%s' and/or Account 'testdao2.testnet' found.", dao); + + ResponseEntity response = subscriptionsApiSteps.subscribeDao(accountId, accountPublicKey, accountSignature, dao); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.BAD_REQUEST); + subscriptionsApiSteps.assertStringContainsValue(response.getBody(), expectedResponse); + } + + @Test + @Severity(SeverityLevel.CRITICAL) + @Story("User should not be able to delete subscription for account with invalid signature") + void userShouldNotBeAbleToSubscribeToDaoWithInvalidSignature() { + String dao = "ewqeerdel.sputnikv2.testnet"; + String expectedResponse = "Invalid signature"; + String invalidSignature = faker.lorem().characters(12, 24); + + ResponseEntity response = subscriptionsApiSteps.subscribeDao(accountId, accountPublicKey, invalidSignature, dao); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.FORBIDDEN); + subscriptionsApiSteps.assertStringContainsValue(response.getBody(), expectedResponse); + } + + @Test + @Disabled("Looks like a bug, getting 200 instead of 400 status code") + @Severity(SeverityLevel.CRITICAL) + @Story("User should be able to get subscriptions for account") + void userShouldBeAbleToGetSubscriptionsForInvalidAccount() { + String accountId = "testdao3132498.testnet"; + + ResponseEntity response = subscriptionsApiSteps.accountSubscriptions(accountId); + subscriptionsApiSteps.assertResponseStatusCode(response, HttpStatus.BAD_REQUEST); + } +}