소스 검색

Merge branch 'master' into sfedoriv/APIAddSupportForAmazonEC2ComputeCloudService

pull/4/head
Svitlana 4 년 전
부모
커밋
256561fd35
13개의 변경된 파일529개의 추가작업 그리고 37개의 파일을 삭제
  1. +46
    -26
      bubble-server/src/main/java/bubble/dao/account/AccountDAO.java
  2. +55
    -0
      bubble-server/src/main/java/bubble/dao/bill/AccountPaymentArchivedDAO.java
  3. +1
    -1
      bubble-server/src/main/java/bubble/model/account/Account.java
  4. +64
    -0
      bubble-server/src/main/java/bubble/model/bill/AccountPaymentArchived.java
  5. +2
    -1
      bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java
  6. +5
    -3
      bubble-server/src/main/java/bubble/model/bill/Bill.java
  7. +2
    -1
      bubble-server/src/main/java/bubble/model/bill/BubblePlan.java
  8. +16
    -0
      bubble-server/src/main/resources/db/migration/V2020042301__add_account_payment_archived.sql
  9. +60
    -3
      bubble-server/src/test/java/bubble/test/system/AccountDeletionTest.java
  10. +1
    -1
      bubble-server/src/test/resources/models/include/new_account.json
  11. +147
    -0
      bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account_with_payments.json
  12. +129
    -0
      bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account_with_payments.json
  13. +1
    -1
      bubble-server/src/test/resources/models/tests/payment/pay_free.json

+ 46
- 26
bubble-server/src/main/java/bubble/dao/account/AccountDAO.java 파일 보기

@@ -8,6 +8,7 @@ import bubble.cloud.CloudServiceDriver;
import bubble.cloud.compute.ComputeNodeSizeType;
import bubble.dao.account.message.AccountMessageDAO;
import bubble.dao.app.*;
import bubble.dao.bill.AccountPaymentArchivedDAO;
import bubble.dao.bill.BillDAO;
import bubble.dao.cloud.AnsibleRoleDAO;
import bubble.dao.cloud.BubbleDomainDAO;
@@ -16,13 +17,13 @@ import bubble.dao.cloud.CloudServiceDAO;
import bubble.dao.device.DeviceDAO;
import bubble.model.account.*;
import bubble.model.app.*;
import bubble.model.bill.Bill;
import bubble.model.bill.BubblePlan;
import bubble.model.cloud.*;
import bubble.server.BubbleConfiguration;
import bubble.service.SearchService;
import bubble.service.boot.SelfNodeService;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.cache.Refreshable;
import org.cobbzilla.wizard.dao.AbstractCRUDDAO;
@@ -48,6 +49,7 @@ import static java.util.concurrent.TimeUnit.MINUTES;
import static org.cobbzilla.util.daemon.ZillaRuntime.daemon;
import static org.cobbzilla.wizard.model.IdentifiableBase.CTIME_ASC;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
import static org.hibernate.criterion.Restrictions.isNotNull;

@Repository @Slf4j
public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearchableDAO<Account> {
@@ -68,7 +70,6 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc
@Autowired private AccountMessageDAO messageDAO;
@Autowired private DeviceDAO deviceDAO;
@Autowired private SelfNodeService selfNodeService;
@Autowired private BillDAO billDAO;
@Autowired private SearchService searchService;
@Autowired private ReferralCodeDAO referralCodeDAO;

@@ -311,51 +312,67 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc
log.info("copyTemplates completed: "+acct);
}

@Override public void delete(String uuid) {
final Account account = findByUuid(uuid);

@Override public void delete(@NonNull final String uuid) {
// you cannot delete the account that owns the current network
if (account.getUuid().equals(configuration.getThisNetwork().getAccount())) {
throw invalidEx("err.delete.invalid", "cannot delete account ("+account.getUuid()+") that owns current network ("+configuration.getThisNetwork().getUuid()+")", account.getUuid());
if (uuid.equals(configuration.getThisNetwork().getAccount())) {
throw invalidEx("err.delete.invalid",
"cannot delete account that owns current network: "
+ uuid + " - " + configuration.getThisNetwork().getUuid(),
uuid);
}

deleteTransactional(uuid);
searchService.flushCache(this);
}

@Transactional(Transactional.TxType.REQUIRES_NEW)
private void deleteTransactional(@NonNull final String uuid) {
// loading, and actually checking if the account with given UUID exists
final var account = findByUuid(uuid);

// cannot delete account with unpaid bills
final List<Bill> unpaid = billDAO.findUnpaidByAccount(uuid);
final var billDAO = configuration.getBean(BillDAO.class);
final var unpaid = billDAO.findUnpaidByAccount(uuid);
if (!unpaid.isEmpty()) {
throw invalidEx("err.delete.unpaidBills", "cannot delete account ("+account.getUuid()+") with "+unpaid.size()+" unpaid bills", account.getUuid());
throw invalidEx("err.delete.unpaidBills",
"cannot delete account with unpaid bills: " + uuid + " - " + unpaid.size(),
uuid);
}

// for referral codes owned by us, set account to null, leave accountUuid in place
final List<ReferralCode> ownedCodes = referralCodeDAO.findByAccount(uuid);
for (ReferralCode c : ownedCodes) referralCodeDAO.update(c.setAccount(null));
final var ownedCodes = referralCodeDAO.findByAccount(uuid);
for (var c : ownedCodes) referralCodeDAO.update(c.setAccount(null));

// for referral a code we used, set usedBy to null, leave usedByUuid in place
final ReferralCode usedCode = referralCodeDAO.findCodeUsedBy(uuid);
final var usedCode = referralCodeDAO.findCodeUsedBy(uuid);
if (usedCode != null) referralCodeDAO.update(usedCode.setClaimedBy(null));

// stash the deletion policy for later use, the policy object will be deleted in deleteDependencies
final AccountDeletionPolicy deletionPolicy = policyDAO.findSingleByAccount(uuid).getDeletionPolicy();
final var deletionPolicy = policyDAO.findSingleByAccount(uuid).getDeletionPolicy();

// archive all payment data for the account just on the first deletion request:
configuration.getBean(AccountPaymentArchivedDAO.class).createForAccount(account);

log.info("delete ("+currentThread().getName()+"): starting to delete account-dependent objects");
log.info("delete: starting to delete account-dependent objects - " + currentThread().getName());
configuration.deleteDependencies(account);
log.info("delete: finished deleting account-dependent objects");
log.info("delete: finished deleting account-dependent objects - " + currentThread().getName());

switch (deletionPolicy) {
case full_delete:
super.delete(uuid);
break;
case block_delete: default:
return;
default:
// includes case block_delete
update(account.setParent(null)
.setAdmin(null)
.setSuspended(null)
.setDescription(null)
.setDeleted()
.setUrl(null)
.setAutoUpdatePolicy(EMPTY_AUTO_UPDATE_POLICY)
.setHashedPassword(HashedPassword.DELETED));
break;
.setAdmin(null)
.setSuspended(null)
.setDescription(null)
.setDeleted()
.setUrl(null)
.setAutoUpdatePolicy(EMPTY_AUTO_UPDATE_POLICY)
.setHashedPassword(HashedPassword.DELETED));
return;
}
searchService.flushCache(this);
}

// once activated (any accounts exist), you can never go back
@@ -413,4 +430,7 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc
}
}

@NonNull public List<Account> findDeleted() {
return list(criteria().add(isNotNull("deleted")));
}
}

+ 55
- 0
bubble-server/src/main/java/bubble/dao/bill/AccountPaymentArchivedDAO.java 파일 보기

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.dao.bill;

import bubble.model.account.Account;
import bubble.model.bill.AccountPaymentArchived;
import lombok.NonNull;
import org.cobbzilla.wizard.dao.AbstractCRUDDAO;
import org.cobbzilla.wizard.dao.SqlViewSearchableDAO;
import org.hibernate.criterion.Order;
import org.springframework.stereotype.Repository;

import static org.cobbzilla.util.daemon.ZillaRuntime.die;

@Repository
public class AccountPaymentArchivedDAO
extends AbstractCRUDDAO<AccountPaymentArchived>
implements SqlViewSearchableDAO<AccountPaymentArchived> {

// newest first
@Override public Order getDefaultSortOrder() { return ORDER_CTIME_DESC; }

public AccountPaymentArchived findByAccountUuid(@NonNull final String accountUuid) {
return findByUniqueField("accountUuid", accountUuid);
}

@NonNull public AccountPaymentArchived createForAccount(@NonNull final Account account) {
final var allBills = getConfiguration().getBean(BillDAO.class).findByAccount(account.getUuid());
final var allPayments = getConfiguration().getBean(AccountPaymentDAO.class).findByAccount(account.getUuid());
final var allMethods = getConfiguration().getBean(AccountPaymentMethodDAO.class)
.findByAccount(account.getUuid());

// Payment info should be present and archived only for currently non deleted account. So, the first deletion
// request will archive those. Any call after that for already deleted account that has some payment info is
// strange and is most probably result of an error - deleted account should not be able to create any payment
// records.
if (account.deleted()) {
if (allBills.size() + allPayments.size() + allMethods.size() > 0) {
return die("Payment records present for already deleted account " + account.getUuid());
// Stopping further execution to avoid loss of data. Check these payment entries and then manually
// decide what to do with those. Call delete again after these are cleared from database.
}
// else, just return already existing entry. Note that any deleted account should have an entry here, while
// such entries might have empty arrays for bills, payment and payment methods.
return findByAccountUuid(account.getUuid());
}
// Finally, create new entry here only if this is the first deletion call for the specified account:
return create(new AccountPaymentArchived().setAccountUuid(account.getUuid())
.setBills(allBills)
.setPayments(allPayments)
.setPaymentMethods(allMethods));
}
}

+ 1
- 1
bubble-server/src/main/java/bubble/model/account/Account.java 파일 보기

@@ -108,7 +108,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci

@ECSearchable(filter=true) @ECField(index=10)
@HasValue(message="err.name.required")
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=100)
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=NAME_MAX_LENGTH)
@Getter private String name;
public Account setName (String n) { this.name = n == null ? null : n.toLowerCase(); return this; }
public boolean hasName () { return !empty(name); }


+ 64
- 0
bubble-server/src/main/java/bubble/model/bill/AccountPaymentArchived.java 파일 보기

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.model.bill;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.cobbzilla.wizard.model.IdentifiableBase;
import org.cobbzilla.wizard.model.entityconfig.EntityFieldType;
import org.cobbzilla.wizard.model.entityconfig.annotations.*;
import org.hibernate.annotations.Type;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Transient;
import java.util.List;

import static bubble.ApiConstants.DB_JSON_MAPPER;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING;

@ECType(root=true) @ECTypeCreate(method="DISABLED")
@ECTypeURIs(listFields={"accountUuid", "ctime"})
@Entity @Accessors(chain=true) @NoArgsConstructor
public class AccountPaymentArchived extends IdentifiableBase {

@ECSearchable @ECField(index=10, type=EntityFieldType.opaque_string)
@ECIndex(unique=true) @Column(unique=true, updatable=false, length=UUID_MAXLEN)
@Getter @Setter private String accountUuid;

@ECSearchable @ECField(index=20, type=EntityFieldType.opaque_string)
@Type(type=ENCRYPTED_STRING)
@Column(updatable=false, nullable=false, columnDefinition="varchar") // no length limit
@JsonIgnore @Getter @Setter private String billsJson;

@Transient public Bill[] getBills() { return json(billsJson, Bill[].class); }
public AccountPaymentArchived setBills(List<Bill> bills) { return setBillsJson(json(bills, DB_JSON_MAPPER)); }

@ECSearchable @ECField(index=30)
@Type(type=ENCRYPTED_STRING)
@Column(updatable=false, nullable=false, columnDefinition="varchar") // no length limit
@JsonIgnore @Getter @Setter private String paymentsJson;

@Transient public AccountPayment[] getPayments() { return json(paymentsJson, AccountPayment[].class); }
public AccountPaymentArchived setPayments(List<AccountPayment> payments) {
return setPaymentsJson(json(payments, DB_JSON_MAPPER));
}

@ECSearchable @ECField(index=40)
@Type(type=ENCRYPTED_STRING)
@Column(updatable=false, nullable=false, columnDefinition="varchar") // no length limit
@JsonIgnore @Getter @Setter private String paymentMethodsJson;

@Transient public AccountPaymentMethod[] getPaymentMethods() {
return json(paymentMethodsJson, AccountPaymentMethod[].class);
}
public AccountPaymentArchived setPaymentMethods(List<AccountPaymentMethod> paymentMethods) {
return setPaymentMethodsJson(json(paymentMethods, DB_JSON_MAPPER));
}
}

+ 2
- 1
bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java 파일 보기

@@ -83,7 +83,8 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount

public static final String DEFAULT_MASKED_PAYMENT_INFO = "XXXX-".repeat(3)+"XXXX";
@ECSearchable @ECField(index=50, type=EntityFieldType.opaque_string)
@Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(100+ENC_PAD)+") NOT NULL")
@Type(type=ENCRYPTED_STRING)
@Column(updatable=false, columnDefinition="varchar(" + (NAME_MAXLEN + ENC_PAD) + ") NOT NULL")
@Getter @Setter private String maskedPaymentInfo = DEFAULT_MASKED_PAYMENT_INFO;

@ECSearchable @ECField(index=60)


+ 5
- 3
bubble-server/src/main/java/bubble/model/bill/Bill.java 파일 보기

@@ -30,6 +30,8 @@ import static org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKeySe
})
public class Bill extends IdentifiableBase implements HasAccountNoName {

public static final int PERIOD_FIELDS_MAX_LENGTH = 20;

@ECSearchable(fkDepth=shallow) @ECField(index=10)
@ECForeignKey(entity=Account.class)
@Column(nullable=false, updatable=false, length=UUID_MAXLEN)
@@ -53,15 +55,15 @@ public class Bill extends IdentifiableBase implements HasAccountNoName {
public boolean unpaid() { return !paid(); }

@ECSearchable @ECField(index=50, type=EntityFieldType.opaque_string)
@Column(nullable=false, updatable=false, length=20)
@Column(nullable=false, updatable=false, length=PERIOD_FIELDS_MAX_LENGTH)
@ECIndex @Getter @Setter private String periodLabel;

@ECSearchable @ECField(index=60, type=EntityFieldType.opaque_string)
@Column(nullable=false, updatable=false, length=20)
@Column(nullable=false, updatable=false, length=PERIOD_FIELDS_MAX_LENGTH)
@Getter @Setter private String periodStart;

@ECSearchable @ECField(index=70, type=EntityFieldType.opaque_string)
@Column(nullable=false, updatable=false, length=20)
@Column(nullable=false, updatable=false, length=PERIOD_FIELDS_MAX_LENGTH)
@Getter @Setter private String periodEnd;

public int daysInPeriod () { return BillPeriod.daysInPeriod(periodStart, periodEnd); }


+ 2
- 1
bubble-server/src/main/java/bubble/model/bill/BubblePlan.java 파일 보기

@@ -41,6 +41,7 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
@Entity @NoArgsConstructor @Accessors(chain=true)
public class BubblePlan extends IdentifiableBaseParentEntity implements HasAccount, HasPriority {

public static final int PLAN_NAME_MAX_LENGTH = 200;
public static final int MAX_CHARGENAME_LEN = 12;

public static final String[] UPDATE_FIELDS = {
@@ -59,7 +60,7 @@ public class BubblePlan extends IdentifiableBaseParentEntity implements HasAccou

@ECSearchable(filter=true) @ECField(index=10)
@HasValue(message="err.name.required")
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=200)
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=PLAN_NAME_MAX_LENGTH)
@Getter @Setter private String name;

@ECSearchable @ECField(index=20)


+ 16
- 0
bubble-server/src/main/resources/db/migration/V2020042301__add_account_payment_archived.sql 파일 보기

@@ -0,0 +1,16 @@
-- Copyright (c) 2020 Bubble, Inc. All rights reserved.
-- For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/

CREATE TABLE public.account_payment_archived (
uuid character varying(100) NOT NULL,
ctime bigint NOT NULL,
mtime bigint NOT NULL,
account_uuid character varying(100),
bills_json character varying NOT NULL,
payment_methods_json character varying NOT NULL,
payments_json character varying NOT NULL
);
ALTER TABLE account_payment_archived OWNER TO bubble;
ALTER TABLE account_payment_archived ADD CONSTRAINT account_payment_archived_pkey PRIMARY KEY (uuid);
ALTER TABLE account_payment_archived ADD CONSTRAINT account_payment_archived_uk_account UNIQUE (account_uuid);
CREATE UNIQUE INDEX account_payment_archived_uniq_account_uuid ON account_payment_archived USING btree (account_uuid);

+ 60
- 3
bubble-server/src/test/java/bubble/test/system/AccountDeletionTest.java 파일 보기

@@ -1,19 +1,76 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.test.system;

import bubble.dao.account.AccountDAO;
import bubble.dao.bill.AccountPaymentArchivedDAO;
import bubble.model.account.Account;
import bubble.model.bill.AccountPayment;
import bubble.model.bill.AccountPaymentMethod;
import bubble.model.bill.Bill;
import bubble.test.ActivatedBubbleModelTestBase;
import lombok.extern.slf4j.Slf4j;
import lombok.NonNull;
import org.junit.Before;
import org.junit.Test;

@Slf4j
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class AccountDeletionTest extends ActivatedBubbleModelTestBase {

@Override protected String getManifest() { return "manifest-test"; }

@Before public void truncatePaymentArchive() {
final var archivedInfoDAO = getBean(AccountPaymentArchivedDAO.class);
archivedInfoDAO.delete(archivedInfoDAO.findAll());
}

@Test public void testFullAccountDeletion() throws Exception { modelTest("account_deletion/full_delete_account"); }
@Test public void testBlockAccountDeletion() throws Exception { modelTest("account_deletion/block_delete_account"); }

@Test public void testBlockDeleteAccountWithPayments() throws Exception {
checkArchivedPayments(modelTest("account_deletion/block_delete_account_with_payments"));
}

@Test public void testFullDeleteAccountWithPayments() throws Exception {
checkArchivedPayments(modelTest("account_deletion/full_delete_account_with_payments"));
}

private void checkArchivedPayments(@NonNull final Map<String, Object> modelTestCtx) {
final var accountDAO = getBean(AccountDAO.class);
final var archivedInfoDAO = getBean(AccountPaymentArchivedDAO.class);

final var deletedAccounts = accountDAO.findDeleted();
// the account was fully deleted at the end of the JSON test
assertEquals("Wrong number of deleted accounts found", 0, deletedAccounts.size());
final var deletedAccount = (Account) modelTestCtx.get("testAccount");

// there should be just 1 archived payment info records corresponding to that 1 deleted account
assertEquals("Archived payments record not created for deleted user", 1, archivedInfoDAO.countAll().intValue());

final var archivedInfo = archivedInfoDAO.findByAccountUuid(deletedAccount.getUuid());
assertNotNull("Archived payment info not found for deleted user", archivedInfo);

final var archivedBills = archivedInfo.getBills();
assertEquals("Only 1 bill should be in for deleted account", 1, archivedBills.length);
assertEquals("Wrong bill archived", ((Bill[]) modelTestCtx.get("bills"))[0], archivedBills[0]);

final var archivedPayments = archivedInfo.getPayments();
assertEquals("Only 1 payment should be in for deleted account", 1, archivedPayments.length);
assertEquals("Wrong payment archived",
((AccountPayment[]) modelTestCtx.get("payments"))[0], archivedPayments[0]);
assertEquals("Archived payment should be for archived bill",
archivedBills[0].getUuid(), archivedPayments[0].getBill());

final var archivedPaymentMethods = archivedInfo.getPaymentMethods();
assertEquals("Only 1 payment method should be in for deleted account", 1, archivedPaymentMethods.length);
assertEquals("Wrong payment method archived",
((AccountPaymentMethod[]) modelTestCtx.get("paymentMethods"))[0], archivedPaymentMethods[0]);
assertEquals("Archived payment method should be for used within archived payment",
archivedPayments[0].getPaymentMethod(), archivedPaymentMethods[0].getUuid());
}
}

+ 1
- 1
bubble-server/src/test/resources/models/include/new_account.json 파일 보기

@@ -77,7 +77,7 @@
"before": "sleep 1s",
"comment": "as root, check inbox for initial email verification message",
"request": {
"session": "rootSession",
"session": "<<rootSessionName>>",
"uri": "debug/inbox/email/{{user.policy.firstEmail}}?action=verify"
},
"response": {


+ 147
- 0
bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account_with_payments.json 파일 보기

@@ -0,0 +1,147 @@
[
{
"comment": "create a user account to block delete later on",
"request": {
"uri": "users",
"method": "put",
"entity": {
"name": "user_with_payment_to_block_delete",
"password": "password1!",
"agreeToTerms": true,
"contact": { "type": "email", "info": "user_with_payment_to_block_delete@example.com" }
}
},
"response": { "store": "testAccount" }
},

{
"comment": "login as that new user",
"request": {
"session": "new",
"uri": "auth/login",
"entity": { "name": "{{ testAccount.name }}", "password": "password1!" }
},
"response": { "store": "testAccount", "sessionName": "userSession", "session": "token" }
},

{
"comment": "get plans",
"request": { "uri": "plans" },
"response": { "store": "plans", "check": [{ "condition": "len(json) >= 1" }] }
},

{
"comment": "add plan, using 'free' payment method",
"request": {
"uri": "me/plans",
"method": "put",
"entity": {
"name": "test-net-{{rand 5}}",
"domain": "{{defaultDomain}}",
"locale": "en_US",
"timezone": "EST",
"plan": "{{plans.[0].name}}",
"footprint": "US",
"paymentMethodObject": { "paymentMethodType": "free", "paymentInfo": "free" }
}
},
"response": { "store": "accountPlan" }
},

{
"comment": "as root, verify bill exists and is paid",
"before": "sleep 15s",
"request": { "session": "rootSession", "uri": "users/{{testAccount.uuid}}/bills" },
"response": {
"store": "bills",
"check": [
{ "condition": "len(json) === 1" },
{ "condition": "json[0].getPlan() === plans[0].getUuid()" },
{ "condition": "json[0].getAccountPlan() === accountPlan.getUuid()" },
{ "condition": "json[0].getTotal() === plans[0].getPrice()" },
{ "condition": "json[0].getStatus().name() === 'paid'" }
]
}
},

{
"comment": "verify payment exists and is successful",
"request": { "uri": "users/{{testAccount.uuid}}/payments" },
"response": {
"store": "payments",
"check": [
{ "condition": "len(json) === 1" },
{ "condition": "json[0].getPlan() === plans[0].getUuid()" },
{ "condition": "json[0].getAccountPlan() === accountPlan.getUuid()" },
{ "condition": "json[0].getAmount() === plans[0].getPrice()" },
{ "condition": "json[0].getStatus().name() === 'success'" },
{ "condition": "json[0].getBill() === bills[0].getUuid()" }
]
}
},

{
"comment": "verify account payment methods, should be one",
"request": { "uri": "users/{{testAccount.uuid}}/paymentMethods" },
"response": {
"store": "paymentMethods",
"check": [
{ "condition": "len(json) === 1" },
{ "condition": "json[0].getPaymentMethodType().name() === 'free'" },
{ "condition": "json[0].getMaskedPaymentInfo() === 'XXXXXXXX'" },
{ "condition": "json[0].getUuid() === payments[0].getPaymentMethod()" }
]
}
},

{
"comment": "now (block) delete the account",
"request": {
"uri": "users/{{testAccount.uuid}}",
"method": "delete"
}
},

{
"comment": "lookup user, expect that it is still there, just marked as deleted",
"request": { "uri": "users/{{testAccount.uuid}}" },
"response": {
"check": [
{ "condition": "json.getUuid() === testAccount.getUuid()" },
{ "condition": "json.getName() === testAccount.getName()" },
{ "condition": "json.deleted()" }
]
}
},

{
"comment": "look up for deleted account's bills - none",
"request": { "uri": "users/{{testAccount.uuid}}/bills" },
"response": { "check": [{ "condition": "len(json) === 0" }] }
},

{
"comment": "look up for deleted account's payments - none",
"request": { "uri": "users/{{testAccount.uuid}}/payments" },
"response": { "check": [{ "condition": "len(json) === 0" }] }
},

{
"comment": "look up for deleted account's payment methods - none",
"request": { "uri": "users/{{testAccount.uuid}}/paymentMethods" },
"response": { "check": [{ "condition": "len(json) === 0" }] }
},

{
"comment": "try deleting the same account again - expect fully deletion this time even without policy",
"request": { "uri": "users/{{testAccount.uuid}}", "method": "delete" }
},

{
"comment": "lookup user, expect there's no such user now",
"request": { "uri": "users/{{testAccount.uuid}}" },
"response": { "status": 404 }
}

// test continues within Java's JUnit test as there are not resource methods implemented for archived payment data
]

+ 129
- 0
bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account_with_payments.json 파일 보기

@@ -0,0 +1,129 @@
[
{
"comment": "create a user account",
"request": {
"uri": "users",
"method": "put",
"entity": {
"name": "user_with_payment_to_delete",
"password": "password1!",
"agreeToTerms": true,
"contact": { "type": "email", "info": "user_with_payment_to_delete@example.com" }
}
},
"response": { "store": "testAccount" }
},

{
"comment": "login as new user",
"request": {
"session": "new",
"uri": "auth/login",
"entity": { "name": "{{ testAccount.name }}", "password": "password1!" }
},
"response": {
"store": "testAccount",
"sessionName": "userSession",
"session": "token"
}
},

{
"comment": "get plans",
"request": { "uri": "plans" },
"response": { "store": "plans", "check": [{ "condition": "len(json) >= 1" }] }
},

{
"comment": "add plan, using 'free' payment method",
"request": {
"uri": "me/plans",
"method": "put",
"entity": {
"name": "test-net-{{rand 5}}",
"domain": "{{defaultDomain}}",
"locale": "en_US",
"timezone": "EST",
"plan": "{{plans.[0].name}}",
"footprint": "US",
"paymentMethodObject": { "paymentMethodType": "free", "paymentInfo": "free" }
}
},
"response": { "store": "accountPlan" }
},

{
"comment": "as root, verify bill exists and is paid",
"before": "sleep 15s",
"request": { "session": "rootSession", "uri": "users/{{testAccount.uuid}}/bills" },
"response": {
"store": "bills",
"check": [
{ "condition": "len(json) === 1" },
{ "condition": "json[0].getPlan() === plans[0].getUuid()" },
{ "condition": "json[0].getAccountPlan() === accountPlan.getUuid()" },
{ "condition": "json[0].getTotal() === plans[0].getPrice()" },
{ "condition": "json[0].getStatus().name() === 'paid'" }
]
}
},

{
"comment": "verify payment exists and is successful",
"request": { "uri": "users/{{testAccount.uuid}}/payments" },
"response": {
"store": "payments",
"check": [
{ "condition": "len(json) === 1" },
{ "condition": "json[0].getPlan() === plans[0].getUuid()" },
{ "condition": "json[0].getAccountPlan() === accountPlan.getUuid()" },
{ "condition": "json[0].getAmount() === plans[0].getPrice()" },
{ "condition": "json[0].getStatus().name() === 'success'" },
{ "condition": "json[0].getBill() === bills[0].getUuid()" }
]
}
},

{
"comment": "verify account payment methods, should be one",
"request": { "uri": "users/{{testAccount.uuid}}/paymentMethods" },
"response": {
"store": "paymentMethods",
"check": [
{ "condition": "len(json) === 1" },
{ "condition": "json[0].getPaymentMethodType().name() === 'free'" },
{ "condition": "json[0].getMaskedPaymentInfo() === 'XXXXXXXX'" },
{ "condition": "json[0].getUuid() === payments[0].getPaymentMethod()" }
]
}
},

{
"comment": "look up that account's policy",
"request": { "uri": "users/{{testAccount.uuid}}/policy" },
"response": { "store": "policy", "check": [{ "condition": "len(json.getAccountContacts()) == 1" }] }
},

{
"comment": "set deletion policy to full_delete for that account",
"request": {
"uri": "users/{{testAccount.uuid}}/policy",
"data": "policy",
"entity": { "deletionPolicy": "full_delete" }
},
"response": { "store": "policy", "check": [{ "condition": "json.getDeletionPolicy().name() == 'full_delete'" }] }
},

{
"comment": "now (full) delete the account",
"request": { "uri": "users/{{testAccount.uuid}}", "method": "delete" }
},

{
"comment": "lookup user, expect there's no such user now",
"request": { "uri": "users/{{testAccount.uuid}}" },
"response": { "status": 404 }
}

// test continues within Java's JUnit test as there are not resource methods implemented for archived payment data
]

+ 1
- 1
bubble-server/src/test/resources/models/tests/payment/pay_free.json 파일 보기

@@ -147,8 +147,8 @@
},

{
"before": "sleep 15s",
"comment": "verify account plans, should be one, verify enabled",
"before": "sleep 15s",
"request": { "uri": "me/plans" },
"response": {
"check": [


불러오는 중...
취소
저장