From e59bfa22dfc2d3448541c8eb443049fb9bb7002a Mon Sep 17 00:00:00 2001 From: Harald Musum Date: Sat, 27 Jun 2020 16:10:55 +0200 Subject: Revert "Add BillingApiHandler" --- .../restapi/billing/BillingApiHandler.java | 396 --------------------- .../controller/security/CloudAccessControl.java | 8 +- .../integration/ServiceRegistryMock.java | 16 +- .../restapi/billing/BillingApiHandlerTest.java | 214 ----------- .../restapi/billing/responses/billing-all-tenants | 48 --- .../billing/responses/invoice-creation-response | 1 - .../restapi/billing/responses/line-item-list | 9 - .../restapi/billing/responses/tenant-billing-view | 38 -- 8 files changed, 12 insertions(+), 718 deletions(-) delete mode 100644 controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view (limited to 'controller-server/src') diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java deleted file mode 100644 index ccbee15d2c5..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.restapi.billing; - -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.TenantName; -import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.container.jdisc.LoggingRequestHandler; -import com.yahoo.container.logging.AccessLog; -import com.yahoo.restapi.ErrorResponse; -import com.yahoo.restapi.JacksonJsonResponse; -import com.yahoo.restapi.MessageResponse; -import com.yahoo.restapi.Path; -import com.yahoo.restapi.SlimeJsonResponse; -import com.yahoo.restapi.StringResponse; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Inspector; -import com.yahoo.slime.Slime; -import com.yahoo.slime.SlimeUtils; -import com.yahoo.vespa.hosted.controller.ApplicationController; -import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PaymentInstrument; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Invoice; -import com.yahoo.vespa.hosted.controller.api.integration.billing.InstrumentOwner; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; -import com.yahoo.yolean.Exceptions; - -import javax.ws.rs.BadRequestException; -import javax.ws.rs.ForbiddenException; -import javax.ws.rs.NotFoundException; -import java.io.IOException; -import java.math.BigDecimal; -import java.security.Principal; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.Executor; -import java.util.logging.Level; - -/** - * @author andreer - * @author olaa - */ -public class BillingApiHandler extends LoggingRequestHandler { - - private static final String OPTIONAL_PREFIX = "/api"; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - - - private final BillingController billingController; - private final ApplicationController applicationController; - - public BillingApiHandler(Executor executor, - AccessLog accessLog, - Controller controller) { - super(executor, accessLog); - this.billingController = controller.serviceRegistry().billingController(); - this.applicationController = controller.applications(); - } - - @Override - public HttpResponse handle(HttpRequest request) { - try { - Path path = new Path(request.getUri(), OPTIONAL_PREFIX); - String userId = userIdOrThrow(request); - switch (request.getMethod()) { - case GET: - return handleGET(request, path, userId); - case PATCH: - return handlePATCH(request, path, userId); - case DELETE: - return handleDELETE(path, userId); - case POST: - return handlePOST(path, request, userId); - default: - return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); - } - } catch (NotFoundException e) { - return ErrorResponse.notFoundError(Exceptions.toMessageString(e)); - } catch (IllegalArgumentException e) { - return ErrorResponse.badRequest(Exceptions.toMessageString(e)); - } catch (Exception e) { - log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); - // Don't expose internal billing details in error message to user - return ErrorResponse.internalServerError("Internal problem while handling billing API request"); - } - } - - private HttpResponse handleGET(HttpRequest request, Path path, String userId) { - if (path.matches("/billing/v1/tenant/{tenant}/token")) return getToken(path.get("tenant"), userId); - if (path.matches("/billing/v1/tenant/{tenant}/instrument")) return getInstruments(path.get("tenant"), userId); - if (path.matches("/billing/v1/tenant/{tenant}/billing")) return getBilling(path.get("tenant"), request.getProperty("until")); - if (path.matches("/billing/v1/tenant/{tenant}/plan")) return getPlan(path.get("tenant")); - if (path.matches("/billing/v1/billing")) return getBillingAllTenants(request.getProperty("until")); - if (path.matches("/billing/v1/invoice/tenant/{tenant}/line-item")) return getLineItems(path.get("tenant")); - return ErrorResponse.notFoundError("Nothing at " + path); - } - - private HttpResponse handlePATCH(HttpRequest request, Path path, String userId) { - if (path.matches("/billing/v1/tenant/{tenant}/instrument")) return patchActiveInstrument(request, path.get("tenant"), userId); - if (path.matches("/billing/v1/tenant/{tenant}/plan")) return patchPlan(request, path.get("tenant")); - return ErrorResponse.notFoundError("Nothing at " + path); - - } - - private HttpResponse handleDELETE(Path path, String userId) { - if (path.matches("/billing/v1/tenant/{tenant}/instrument/{instrument}")) return deleteInstrument(path.get("tenant"), userId, path.get("instrument")); - if (path.matches("/billing/v1/invoice/line-item/{line-item-id}")) return deleteLineItem(path.get("line-item-id")); - return ErrorResponse.notFoundError("Nothing at " + path); - - } - - private HttpResponse handlePOST(Path path, HttpRequest request, String userId) { - if (path.matches("/billing/v1/invoice")) return createInvoice(request, userId); - if (path.matches("/billing/v1/invoice/{invoice-id}/status")) return setInvoiceStatus(request, path.get("invoice-id")); - if (path.matches("/billing/v1/invoice/tenant/{tenant}/line-item")) return addLineItem(request, path.get("tenant")); - return ErrorResponse.notFoundError("Nothing at " + path); - - } - - private HttpResponse getPlan(String tenant) { - var plan = billingController.getPlan(TenantName.from(tenant)); - var slime = new Slime(); - var root = slime.setObject(); - root.setString("tenant", tenant); - root.setString("plan", plan.value()); - return new SlimeJsonResponse(slime); - } - - private HttpResponse patchPlan(HttpRequest request, String tenant) { - var tenantName = TenantName.from(tenant); - var slime = inspectorOrThrow(request); - var planId = PlanId.from(slime.field("plan").asString()); - var hasApplications = applicationController.asList(tenantName).size() > 0; - - if (billingController.setPlan(tenantName, planId, hasApplications)) { - return new StringResponse("Plan: " + planId.value()); - } else { - return ErrorResponse.forbidden("Invalid plan change with active deployments"); - } - - } - - private HttpResponse getBillingAllTenants(String until) { - try { - var untilDate = untilParameter(until); - var uncommittedInvoices = billingController.createUncommittedInvoices(untilDate); - - var slime = new Slime(); - var root = slime.setObject(); - root.setString("until", untilDate.format(DateTimeFormatter.ISO_DATE)); - var tenants = root.setArray("tenants"); - - uncommittedInvoices.forEach((tenant, invoice) -> { - var tc = tenants.addObject(); - tc.setString("tenant", tenant.value()); - getPlanForTenant(tc, tenant); - renderCurrentUsage(tc.setObject("current"), invoice); - renderAdditionalItems(tc.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenant)); - - billingController.getDefaultInstrument(tenant).ifPresent(card -> - renderInstrument(tc.setObject("payment"), card) - ); - }); - - return new SlimeJsonResponse(slime); - } catch (DateTimeParseException e) { - return ErrorResponse.badRequest("Could not parse date: " + until); - } - } - - private HttpResponse addLineItem(HttpRequest request, String tenant) { - Inspector inspector = inspectorOrThrow(request); - billingController.addLineItem( - TenantName.from(tenant), - getInspectorFieldOrThrow(inspector, "description"), - new BigDecimal(getInspectorFieldOrThrow(inspector, "amount")), - userIdOrThrow(request)); - return new MessageResponse("Added line item for tenant " + tenant); - } - - private HttpResponse setInvoiceStatus(HttpRequest request, String invoiceId) { - Inspector inspector = inspectorOrThrow(request); - String status = getInspectorFieldOrThrow(inspector, "status"); - billingController.updateInvoiceStatus(Invoice.Id.of(invoiceId), userIdOrThrow(request), status); - return new MessageResponse("Updated status of invoice " + invoiceId); - } - - private HttpResponse createInvoice(HttpRequest request, String userId) { - Inspector inspector = inspectorOrThrow(request); - TenantName tenantName = TenantName.from(getInspectorFieldOrThrow(inspector, "tenant")); - - LocalDate startDate = LocalDate.parse(getInspectorFieldOrThrow(inspector, "startTime")); - LocalDate endDate = LocalDate.parse(getInspectorFieldOrThrow(inspector, "endTime")); - ZonedDateTime startTime = startDate.atStartOfDay(ZoneId.of("UTC")); - ZonedDateTime endTime = endDate.atStartOfDay(ZoneId.of("UTC")); - - var invoiceId = billingController.createInvoiceForPeriod(tenantName, startTime, endTime, userId); - - return new MessageResponse("Created invoice with ID " + invoiceId.value()); - } - - private HttpResponse getInstruments(String tenant, String userId) { - var instrumentListResponse = billingController.listInstruments(TenantName.from(tenant), userId); - return new JacksonJsonResponse<>(200, instrumentListResponse); - } - - private HttpResponse getToken(String tenant, String userId) { - return new StringResponse(billingController.createClientToken(tenant, userId)); - } - - private HttpResponse getBilling(String tenant, String until) { - try { - var untilDate = untilParameter(until); - var tenantId = TenantName.from(tenant); - var slimeResponse = new Slime(); - var root = slimeResponse.setObject(); - - root.setString("until", untilDate.format(DateTimeFormatter.ISO_DATE)); - - getPlanForTenant(root, tenantId); - renderCurrentUsage(root.setObject("current"), getCurrentUsageForTenant(tenantId, untilDate)); - renderAdditionalItems(root.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenantId)); - renderInvoices(root.setArray("bills"), getInvoicesForTenant(tenantId)); - - billingController.getDefaultInstrument(tenantId).ifPresent( card -> - renderInstrument(root.setObject("payment"), card) - ); - - return new SlimeJsonResponse(slimeResponse); - } catch (DateTimeParseException e) { - return ErrorResponse.badRequest("Could not parse date: " + until); - } - } - - private HttpResponse getLineItems(String tenant) { - var slimeResponse = new Slime(); - var root = slimeResponse.setObject(); - var lineItems = root.setArray("lineItems"); - - billingController.getUnusedLineItems(TenantName.from(tenant)) - .forEach(lineItem -> { - var itemCursor = lineItems.addObject(); - renderLineItemToCursor(itemCursor, lineItem); - }); - - return new SlimeJsonResponse(slimeResponse); - } - - private void getPlanForTenant(Cursor cursor, TenantName tenant) { - cursor.setString("plan", billingController.getPlan(tenant).value()); - } - - private void renderInstrument(Cursor cursor, PaymentInstrument instrument) { - cursor.setString("type", instrument.getType()); - cursor.setString("brand", instrument.getBrand()); - cursor.setString("endingWith", instrument.getEndingWith()); - } - - private void renderCurrentUsage(Cursor cursor, Invoice currentUsage) { - cursor.setString("amount", currentUsage.sum().toPlainString()); - cursor.setString("status", "accrued"); - cursor.setString("from", currentUsage.getStartTime().format(DATE_TIME_FORMATTER)); - var itemsCursor = cursor.setArray("items"); - currentUsage.lineItems().forEach(lineItem -> { - var itemCursor = itemsCursor.addObject(); - renderLineItemToCursor(itemCursor, lineItem); - }); - } - - private void renderAdditionalItems(Cursor cursor, List items) { - items.forEach(item -> { - renderLineItemToCursor(cursor.addObject(), item); - }); - } - - private Invoice getCurrentUsageForTenant(TenantName tenant, LocalDate until) { - return billingController.createUncommittedInvoice(tenant, until); - } - - private List getInvoicesForTenant(TenantName tenant) { - return billingController.getInvoices(tenant); - } - - private void renderInvoices(Cursor cursor, List invoices) { - invoices.forEach(invoice -> { - var invoiceCursor = cursor.addObject(); - renderInvoiceToCursor(invoiceCursor, invoice); - }); - } - - private void renderInvoiceToCursor(Cursor invoiceCursor, Invoice invoice) { - invoiceCursor.setString("id", invoice.id().value()); - invoiceCursor.setString("from", invoice.getStartTime().format(DATE_TIME_FORMATTER)); - invoiceCursor.setString("to", invoice.getEndTime().format(DATE_TIME_FORMATTER)); - - invoiceCursor.setString("amount", invoice.sum().toString()); - invoiceCursor.setString("status", invoice.status()); - var statusCursor = invoiceCursor.setArray("statusHistory"); - renderStatusHistory(statusCursor, invoice.statusHistory()); - - - var lineItemsCursor = invoiceCursor.setArray("items"); - invoice.lineItems().forEach(lineItem -> { - var itemCursor = lineItemsCursor.addObject(); - renderLineItemToCursor(itemCursor, lineItem); - }); - } - - private void renderStatusHistory(Cursor cursor, Invoice.StatusHistory statusHistory) { - statusHistory.getHistory() - .entrySet() - .stream() - .forEach(entry -> { - var c = cursor.addObject(); - c.setString("at", entry.getKey().format(DATE_TIME_FORMATTER)); - c.setString("status", entry.getValue()); - }); - } - - private void renderLineItemToCursor(Cursor cursor, Invoice.LineItem lineItem) { - cursor.setString("id", lineItem.id()); - cursor.setString("description", lineItem.description()); - cursor.setString("amount", lineItem.amount().toString()); - lineItem.applicationId().ifPresent(appId -> { - cursor.setString("application", appId.application().value()); - }); - } - - private HttpResponse deleteInstrument(String tenant, String userId, String instrument) { - if (billingController.deleteInstrument(TenantName.from(tenant), userId, instrument)) { - return new StringResponse("OK"); - } else { - return ErrorResponse.forbidden("Cannot delete payment instrument you don't own"); - } - } - - private HttpResponse deleteLineItem(String lineItemId) { - billingController.deleteLineItem(lineItemId); - return new MessageResponse("Succesfully deleted line item " + lineItemId); - } - - private HttpResponse patchActiveInstrument(HttpRequest request, String tenant, String userId) { - var inspector = inspectorOrThrow(request); - String instrumentId = getInspectorFieldOrThrow(inspector, "active"); - InstrumentOwner paymentInstrument = new InstrumentOwner(TenantName.from(tenant), userId, instrumentId, true); - boolean success = billingController.setActivePaymentInstrument(paymentInstrument); - return success ? new StringResponse("OK") : ErrorResponse.internalServerError("Failed to patch active instrument"); - } - - private Inspector inspectorOrThrow(HttpRequest request) { - try { - return SlimeUtils.jsonToSlime(request.getData().readAllBytes()).get(); - } catch (IOException e) { - throw new BadRequestException("Failed to parse request body"); - } - } - - private static String userIdOrThrow(HttpRequest request) { - return Optional.ofNullable(request.getJDiscRequest().getUserPrincipal()) - .map(Principal::getName) - .orElseThrow(() -> new ForbiddenException("Must be authenticated to use this API")); - } - - private static String getInspectorFieldOrThrow(Inspector inspector, String field) { - if (!inspector.field(field).valid()) - throw new BadRequestException("Field " + field + " cannot be null"); - return inspector.field(field).asString(); - } - - private DeploymentId getDeploymentIdOrNull(Inspector inspector) { - if (inspector.field("applicationId").valid() != inspector.field("zoneId").valid() ) { - throw new BadRequestException("Either both application id and zone id should be set, or neither."); - } - if (inspector.field("applicationId").valid()) { - return new DeploymentId( - ApplicationId.fromSerializedForm(inspector.field("applicationId").asString()), - com.yahoo.config.provision.zone.ZoneId.from(inspector.field("zoneId").asString()) - ); - } - return null; - } - - private LocalDate untilParameter(String until) { - if (until == null || until.isEmpty() || until.isBlank()) - return LocalDate.now().plusDays(1); - return LocalDate.parse(until); - } - -} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java index 13cf992cd52..bd0143ef879 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java @@ -9,7 +9,7 @@ import com.yahoo.vespa.flags.FlagSource; import com.yahoo.vespa.flags.Flags; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; +import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanController; import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInfo; import com.yahoo.vespa.hosted.controller.api.integration.user.Roles; import com.yahoo.vespa.hosted.controller.api.integration.user.UserId; @@ -36,13 +36,13 @@ public class CloudAccessControl implements AccessControl { private final UserManagement userManagement; private final BooleanFlag enablePublicSignup; - private final BillingController planController; + private final PlanController planController; @Inject public CloudAccessControl(UserManagement userManagement, FlagSource flagSource, ServiceRegistry serviceRegistry) { this.userManagement = userManagement; this.enablePublicSignup = Flags.ENABLE_PUBLIC_SIGNUP_FLOW.bindTo(flagSource); - planController = serviceRegistry.billingController(); + planController = serviceRegistry.planController(); } @Override @@ -109,7 +109,7 @@ public class CloudAccessControl implements AccessControl { } private boolean isTrial(TenantName tenant) { - return planController.getPlan(tenant).value().equals("trial"); + return planController.getPlan(tenant).id().equals("trial"); } @Override diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java index 1b21f7db7c4..b7e7c9814e3 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java @@ -12,14 +12,14 @@ import com.yahoo.vespa.hosted.controller.api.integration.aws.MockAwsEventFetcher import com.yahoo.vespa.hosted.controller.api.integration.aws.MockResourceTagger; import com.yahoo.vespa.hosted.controller.api.integration.aws.NoopApplicationRoleService; import com.yahoo.vespa.hosted.controller.api.integration.aws.ResourceTagger; -import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController; +import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanController; import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMock; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServer; import com.yahoo.vespa.hosted.controller.api.integration.dns.MemoryNameService; import com.yahoo.vespa.hosted.controller.api.integration.entity.MemoryEntityService; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockContactRetriever; import com.yahoo.vespa.hosted.controller.api.integration.organization.MockIssueHandler; +import com.yahoo.vespa.hosted.controller.api.integration.organization.SystemMonitor; import com.yahoo.vespa.hosted.controller.api.integration.resource.CostReportConsumerMock; import com.yahoo.vespa.hosted.controller.api.integration.routing.GlobalRoutingService; import com.yahoo.vespa.hosted.controller.api.integration.routing.MemoryGlobalRoutingService; @@ -60,7 +60,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg private final MockRunDataStore mockRunDataStore = new MockRunDataStore(); private final MockResourceTagger mockResourceTagger = new MockResourceTagger(); private final ApplicationRoleService applicationRoleService = new NoopApplicationRoleService(); - private final BillingController billingController = new MockBillingController(); + private final PlanController planController = (tenantName) -> null; public ServiceRegistryMock(SystemName system) { this.zoneRegistryMock = new ZoneRegistryMock(system); @@ -187,11 +187,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg return systemMonitor; } - @Override - public BillingController billingController() { - return billingController; - } - public ConfigServerMock configServerMock() { return configServerMock; } @@ -208,4 +203,9 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg return endpointCertificateMock; } + @Override + public PlanController planController() { + return planController; + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java deleted file mode 100644 index 19cfa95c682..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java +++ /dev/null @@ -1,214 +0,0 @@ -package com.yahoo.vespa.hosted.controller.restapi.billing; - -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Invoice; -import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController; -import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; -import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; -import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -import static com.yahoo.application.container.handler.Request.Method.*; -import static org.junit.Assert.*; - -/** - * @author olaa - */ -public class BillingApiHandlerTest extends ControllerContainerCloudTest { - - private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/"; - private static final TenantName tenant = TenantName.from("tenant1"); - private static final TenantName tenant2 = TenantName.from("tenant2"); - private static final Set tenantRole = Set.of(Role.administrator(tenant)); - private static final Set financeAdmin = Set.of(Role.hostedAccountant()); - private MockBillingController billingController; - - private ContainerTester tester; - - @Before - public void setup() { - tester = new ContainerTester(container, responseFiles); - billingController = (MockBillingController) tester.serviceRegistry().billingController(); - } - - @Override - protected SystemName system() { - return SystemName.PublicCd; - } - - @Override - protected String variablePartXml() { - return " \n" + - " \n" + - - " \n" + - " http://*/billing/v1/*\n" + - " \n" + - - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " http://*/*\n" + - " \n" + - " \n" + - " \n"; - } - - @Test - public void setting_and_deleting_instrument() { - assertTrue(billingController.getDefaultInstrument(tenant).isEmpty()); - - var instrumentRequest = request("/billing/v1/tenant/tenant1/instrument", PATCH) - .data("{\"active\": \"id-1\"}") - .roles(tenantRole); - - tester.assertResponse(instrumentRequest,"OK"); - assertEquals("id-1", billingController.getDefaultInstrument(tenant).get().getId()); - - var deleteInstrumentRequest = request("/billing/v1/tenant/tenant1/instrument/id-1", DELETE) - .roles(tenantRole); - - tester.assertResponse(deleteInstrumentRequest,"OK"); - assertTrue(billingController.getDefaultInstrument(tenant).isEmpty()); - } - - @Test - public void response_list_bills() { - var invoice = createInvoice(); - - billingController.addInvoice(tenant, invoice, true); - billingController.addInvoice(tenant, invoice, false); - billingController.setPlan(tenant, PlanId.from("some-plan"), true); - - var request = request("/billing/v1/tenant/tenant1/billing?until=2020-05-28").roles(tenantRole); - tester.assertResponse(request, new File("tenant-billing-view")); - - } - - @Test - public void test_invoice_creation() { - var invoices = billingController.getInvoices(tenant); - assertEquals(0, invoices.size()); - - String requestBody = "{\"tenant\":\"tenant1\", \"startTime\":\"2020-04-20\", \"endTime\":\"2020-05-20\"}"; - var request = request("/billing/v1/invoice", POST) - .data(requestBody) - .roles(tenantRole); - - tester.assertResponse(request, accessDenied, 403); - request.roles(financeAdmin); - tester.assertResponse(request, new File("invoice-creation-response")); - - invoices = billingController.getInvoices(tenant); - assertEquals(1, invoices.size()); - Invoice invoice = invoices.get(0); - assertEquals(invoice.getStartTime().toString(), "2020-04-20T00:00Z[UTC]"); - assertEquals(invoice.getEndTime().toString(), "2020-05-20T00:00Z[UTC]"); - } - - @Test - public void adding_and_listing_line_item() { - - var requestBody = "{" + - "\"description\":\"some description\"," + - "\"amount\":\"123.45\" " + - "}"; - - var request = request("/billing/v1/invoice/tenant/tenant1/line-item", POST) - .data(requestBody) - .roles(financeAdmin); - - tester.assertResponse(request, "{\"message\":\"Added line item for tenant tenant1\"}"); - - var lineItems = billingController.getUnusedLineItems(tenant); - Assert.assertEquals(1, lineItems.size()); - Invoice.LineItem lineItem = lineItems.get(0); - assertEquals("some description", lineItem.description()); - assertEquals(new BigDecimal("123.45"), lineItem.amount()); - - request = request("/billing/v1/invoice/tenant/tenant1/line-item") - .roles(financeAdmin); - - tester.assertResponse(request, new File("line-item-list")); - } - - @Test - public void adding_new_status() { - billingController.addInvoice(tenant, createInvoice(), true); - - var requestBody = "{\"status\":\"DONE\"}"; - var request = request("/billing/v1/invoice/id-1/status", POST) - .data(requestBody) - .roles(financeAdmin); - tester.assertResponse(request, "{\"message\":\"Updated status of invoice id-1\"}"); - - var invoice = billingController.getInvoices(tenant).get(0); - assertEquals("DONE", invoice.status()); - } - - @Test - public void list_all_uninvoiced_items() { - var invoice = createInvoice(); - billingController.setPlan(tenant, PlanId.from("some-plan"), true); - billingController.setPlan(tenant2, PlanId.from("some-plan"), true); - billingController.addInvoice(tenant, invoice, false); - billingController.addLineItem(tenant, "support", new BigDecimal("42"), "Smith"); - billingController.addInvoice(tenant2, invoice, false); - - - var request = request("/billing/v1/billing?until=2020-05-28").roles(financeAdmin); - - tester.assertResponse(request, new File("billing-all-tenants")); - } - - @Test - public void setting_plans() { - var planRequest = request("/billing/v1/tenant/tenant1/plan", PATCH) - .data("{\"plan\": \"new-plan\"}") - .roles(tenantRole); - tester.assertResponse(planRequest, "Plan: new-plan"); - assertEquals("new-plan", billingController.getPlan(tenant).value()); - } - - private Invoice createInvoice() { - var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneId.systemDefault()); - var end = start.plusDays(5); - var statusHistory = new Invoice.StatusHistory(new TreeMap<>(Map.of(start, "OPEN"))); - return new Invoice( - Invoice.Id.of("id-1"), - statusHistory, - List.of(createLineItem(start)), - start, - end - ); - } - - - private Invoice.LineItem createLineItem(ZonedDateTime addedAt) { - return new Invoice.LineItem( - "some-id", - "description", - new BigDecimal("123.00"), - "plan", - "Smith", - addedAt - ); - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants deleted file mode 100644 index c5bf0c88c2c..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants +++ /dev/null @@ -1,48 +0,0 @@ -{ - "until":"2020-05-28", - "tenants":[ - { - "tenant":"tenant2", - "plan":"some-plan", - "current":{ - "amount":"123.00", - "status":"accrued", - "from":"2020-05-23", - "items":[ - { - "id":"some-id", - "description":"description", - "amount":"123.00" - } - ] - }, - "additional":{"items":[]} - }, - { - "tenant":"tenant1", - "plan":"some-plan", - "current":{ - "amount":"123.00", - "status":"accrued", - "from":"2020-05-23", - "items":[ - { - "id":"some-id", - "description":"description", - "amount":"123.00" - } - ] - }, - "additional": - { - "items":[ - { - "id":"line-item-id", - "description":"support", - "amount":"42.00" - } - ] - } - } - ] -} \ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response deleted file mode 100644 index 0a92229025b..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response +++ /dev/null @@ -1 +0,0 @@ -{"message":"Created invoice with ID id-123"} \ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list deleted file mode 100644 index cd5aec2f8f4..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list +++ /dev/null @@ -1,9 +0,0 @@ -{ - "lineItems":[ - { - "id":"line-item-id", - "description":"some description", - "amount":"123.45" - } - ] -} \ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view deleted file mode 100644 index 8bc39771b31..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view +++ /dev/null @@ -1,38 +0,0 @@ -{ - "until":"2020-05-28", - "plan":"some-plan", - "current":{ - "amount":"123.00", - "status":"accrued", - "from":"2020-05-23", - "items":[ - { - "id":"some-id", - "description":"description", - "amount":"123.00" - } - ] - }, - "additional":{"items":[]}, - "bills":[ - { - "id":"id-1", - "from":"2020-05-23", - "to":"2020-05-28","amount":"123.00", - "status":"OPEN", - "statusHistory":[ - { - "at":"2020-05-23", - "status":"OPEN" - } - ], - "items":[ - { - "id":"some-id", - "description":"description", - "amount":"123.00" - } - ] - } - ] -} \ No newline at end of file -- cgit v1.2.3