From bda99c469758d860ce8a75c163705d5406d376bb Mon Sep 17 00:00:00 2001 From: "Ivan Milosavljevic (TheJavaGuy)" Date: Mon, 2 Mar 2026 21:48:36 +0100 Subject: [PATCH] add Subscription faker (closes #46) --- .../java/org/thejavaguy/javafaker/Faker.java | 11 + .../thejavaguy/javafaker/Subscription.java | 310 ++++++++++++++++++ src/main/resources/en/subscription.yml | 15 +- .../javafaker/SubscriptionTest.java | 205 ++++++++++++ 4 files changed, 536 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/thejavaguy/javafaker/Subscription.java create mode 100644 src/test/java/org/thejavaguy/javafaker/SubscriptionTest.java diff --git a/src/main/java/org/thejavaguy/javafaker/Faker.java b/src/main/java/org/thejavaguy/javafaker/Faker.java index f985aa3..a997e95 100644 --- a/src/main/java/org/thejavaguy/javafaker/Faker.java +++ b/src/main/java/org/thejavaguy/javafaker/Faker.java @@ -108,6 +108,7 @@ public class Faker { private final EnglandFootBall englandfootball; private final Mountain mountain; private final Shipping shipping; + private final Subscription subscription; private final Vehicle vehicle; public Faker() { @@ -227,6 +228,7 @@ public Faker(FakeValuesService fakeValuesService, RandomService random) { this.englandfootball = new EnglandFootBall(this); this.mountain = new Mountain(this); this.shipping = new Shipping(this); + this.subscription = new Subscription(this); this.vehicle = new Vehicle(this); } @@ -737,6 +739,15 @@ public Vehicle vehicle() { return vehicle; } + /** + * Returns a Subscription instance for generating subscription-related fake data. + * + * @return a {@link Subscription} instance + */ + public Subscription subscription() { + return subscription; + } + public String resolve(String key) { return this.fakeValuesService.resolve(key, this, this); } diff --git a/src/main/java/org/thejavaguy/javafaker/Subscription.java b/src/main/java/org/thejavaguy/javafaker/Subscription.java new file mode 100644 index 0000000..d2a332b --- /dev/null +++ b/src/main/java/org/thejavaguy/javafaker/Subscription.java @@ -0,0 +1,310 @@ +package org.thejavaguy.javafaker; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Generates realistic subscription and SaaS-related fake data. + * + * @since 1.x.x + */ +public class Subscription { + + private final Faker faker; + + protected Subscription(Faker faker) { + this.faker = faker; + } + + /** + * Returns a random subscription plan name. + * + * @return plan name, e.g., "Professional", "Enterprise" + */ + public String planName() { + return faker.fakeValuesService().fetchString("subscription.plan_names"); + } + + /** + * Returns a random subscription status. + * + * @return status, e.g., "active", "canceled", "trialing" + */ + public String status() { + return faker.fakeValuesService().fetchString("subscription.statuses"); + } + + /** + * Returns a random payment method. + * + * @return payment method, e.g., "Credit Card", "PayPal" + */ + public String paymentMethod() { + return faker.fakeValuesService().fetchString("subscription.payment_methods"); + } + + /** + * Returns a random billing cycle. + * + * @return billing cycle, e.g., "monthly", "annually" + */ + public String billingCycle() { + return faker.fakeValuesService().fetchString("subscription.billing_cycles"); + } + + /** + * Returns a random payment term. + * + * @return payment term, e.g., "Net 30", "Due on Receipt" + */ + public String paymentTerm() { + return faker.fakeValuesService().fetchString("subscription.payment_terms"); + } + + /** + * Returns a random currency code. + * + * @return currency code, e.g., "USD", "EUR" + */ + public String currency() { + return faker.fakeValuesService().fetchString("subscription.currencies"); + } + + /** + * Returns a random subscription feature. + * + * @return feature, e.g., "Unlimited Users", "API Access" + */ + public String feature() { + return faker.fakeValuesService().fetchString("subscription.features"); + } + + /** + * Returns a random subscription type. + * + * @return subscription type, e.g., "SaaS", "Streaming" + */ + public String subscriptionType() { + return faker.fakeValuesService().fetchString("subscription.subscription_types"); + } + + /** + * Returns a random cancellation reason. + * + * @return cancellation reason, e.g., "Too expensive" + */ + public String cancellationReason() { + return faker.fakeValuesService().fetchString("subscription.cancellation_reasons"); + } + + /** + * Returns a random renewal type. + * + * @return renewal type, e.g., "Auto-renew", "Manual renewal" + */ + public String renewalType() { + return faker.fakeValuesService().fetchString("subscription.renewal_types"); + } + + /** + * Generates a random subscription ID. + * + * @return subscription ID, e.g., "sub_1234567890abcdef" + */ + public String subscriptionId() { + return "sub_" + faker.regexify("[a-z0-9]{16}"); + } + + /** + * Generates a random customer ID. + * + * @return customer ID, e.g., "cus_1234567890abcdef" + */ + public String customerId() { + return "cus_" + faker.regexify("[a-z0-9]{14}"); + } + + /** + * Generates a random invoice ID. + * + * @return invoice ID, e.g., "inv_1234567890abcdef" + */ + public String invoiceId() { + return "inv_" + faker.regexify("[a-z0-9]{14}"); + } + + /** + * Generates a random monthly price between $5 and $500. + * + * @return price as BigDecimal with 2 decimal places + */ + public BigDecimal price() { + return price(5.00, 500.00); + } + + /** + * Generates a random price between min and max. + * + * @param min minimum price + * @param max maximum price + * @return price as BigDecimal with 2 decimal places + */ + public BigDecimal price(double min, double max) { + double price = min + (faker.random().nextDouble() * (max - min)); + return BigDecimal.valueOf(price).setScale(2, RoundingMode.HALF_UP); + } + + /** + * Generates a formatted price string with currency. + * + * @return formatted price, e.g., "$99.99" + */ + public String priceFormatted() { + return "$" + price().toString(); + } + + /** + * Generates a formatted price with specified currency symbol. + * + * @param currencySymbol the currency symbol to use + * @return formatted price, e.g., "€99.99" + */ + public String priceFormatted(String currencySymbol) { + return currencySymbol + price().toString(); + } + + /** + * Generates a subscription start date within the last year. + * + * @return start date as LocalDate + */ + public LocalDate startDate() { + int daysBack = faker.random().nextInt(365); + return LocalDate.now().minusDays(daysBack); + } + + /** + * Generates a formatted subscription start date. + * + * @param pattern the date format pattern + * @return formatted start date string + */ + public String startDate(String pattern) { + return startDate().format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * Generates a subscription end date in the future. + * + * @return end date as LocalDate (1 month to 2 years from now) + */ + public LocalDate endDate() { + int daysAhead = faker.random().nextInt(730) + 30; + return LocalDate.now().plusDays(daysAhead); + } + + /** + * Generates a formatted subscription end date. + * + * @param pattern the date format pattern + * @return formatted end date string + */ + public String endDate(String pattern) { + return endDate().format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * Generates a next billing date in the future. + * + * @return next billing date as LocalDate (1-30 days from now) + */ + public LocalDate nextBillingDate() { + int daysAhead = faker.random().nextInt(30) + 1; + return LocalDate.now().plusDays(daysAhead); + } + + /** + * Generates a trial end date. + * + * @return trial end date as LocalDate (7-30 days from now) + */ + public LocalDate trialEndDate() { + int daysAhead = faker.random().nextInt(24) + 7; + return LocalDate.now().plusDays(daysAhead); + } + + /** + * Generates a random trial period in days. + * + * @return trial period in days (7, 14, or 30) + */ + public int trialPeriodDays() { + int[] periods = {7, 14, 30}; + return periods[faker.random().nextInt(periods.length)]; + } + + /** + * Generates a random discount percentage. + * + * @return discount percentage (5-50) + */ + public int discountPercent() { + int[] discounts = {5, 10, 15, 20, 25, 30, 40, 50}; + return discounts[faker.random().nextInt(discounts.length)]; + } + + /** + * Generates a promo/coupon code. + * + * @return coupon code, e.g., "SAVE20", "PROMO2024" + */ + public String couponCode() { + String[] prefixes = {"SAVE", "PROMO", "DEAL", "OFF", "DISCOUNT", "SPECIAL"}; + String prefix = prefixes[faker.random().nextInt(prefixes.length)]; + int number = faker.random().nextInt(50) + 10; + return prefix + number; + } + + /** + * Generates a random user/seat count for a subscription. + * + * @return user count between 1 and 100 + */ + public int userCount() { + return faker.random().nextInt(100) + 1; + } + + /** + * Generates a random user count within a range. + * + * @param min minimum users + * @param max maximum users + * @return user count within range + */ + public int userCount(int min, int max) { + return faker.random().nextInt(max - min + 1) + min; + } + + /** + * Checks if subscription should auto-renew (random). + * + * @return true or false randomly + */ + public boolean autoRenew() { + return faker.random().nextBoolean(); + } + + /** + * Generates a full plan description combining name and billing cycle. + * + * @return plan description, e.g., "Professional (Monthly)" + */ + public String planDescription() { + String plan = planName(); + String cycle = billingCycle(); + String capitalizedCycle = cycle.substring(0, 1).toUpperCase() + cycle.substring(1); + return plan + " (" + capitalizedCycle + ")"; + } +} diff --git a/src/main/resources/en/subscription.yml b/src/main/resources/en/subscription.yml index 65d9b02..562fb9d 100644 --- a/src/main/resources/en/subscription.yml +++ b/src/main/resources/en/subscription.yml @@ -1,8 +1,13 @@ en: faker: subscription: - plans: ["Free Trial", "Basic", "Starter", "Essential", "Student", "Bronze", "Standard", "Silver", "Gold", "Platinum", "Professional", "Business", "Diamond", "Premium"] - statuses: ["Active", "Idle", "Blocked", "Pending"] - payment_methods: ["Credit card", "Debit card", "Paypal", "Cash", "Money transfer", "Bitcoins", "Cheque", "Apple Pay", "Google Pay", "WeChat Pay", "Alipay", "Visa checkout"] - subscription_terms: ["Daily", "Weekly", "Monthly", "Annual", "Biennal", "Triennal", "Quinquennal", "Lifetime"] - payment_terms: ["Payment in advance", "Monthly", "Annual", "Full subscription"] + plan_names: ["Free", "Starter", "Basic", "Standard", "Professional", "Pro", "Premium", "Business", "Enterprise", "Ultimate", "Growth", "Scale", "Team", "Individual", "Student", "Family", "Unlimited"] + statuses: ["active", "inactive", "canceled", "expired", "past_due", "trialing", "paused", "pending", "suspended"] + payment_methods: ["Credit Card", "Debit Card", "PayPal", "Bank Transfer", "ACH", "Wire Transfer", "Apple Pay", "Google Pay", "Invoice", "Check", "Cryptocurrency"] + billing_cycles: ["monthly", "quarterly", "semi-annually", "annually", "bi-annually", "weekly", "bi-weekly"] + payment_terms: ["Net 15", "Net 30", "Net 45", "Net 60", "Due on Receipt", "Prepaid"] + currencies: ["USD", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "INR", "BRL", "MXN"] + features: ["Unlimited Users", "API Access", "Priority Support", "Custom Branding", "Advanced Analytics", "SSO Integration", "Audit Logs", "Data Export", "Dedicated Account Manager", "Custom Integrations", "99.9% SLA", "24/7 Support", "White Label", "Multi-tenant", "HIPAA Compliance", "SOC 2 Compliance", "Unlimited Storage", "Advanced Security", "Role-based Access", "Custom Workflows"] + subscription_types: ["SaaS", "Streaming", "Membership", "License", "Service", "Content", "Software", "Platform"] + cancellation_reasons: ["Too expensive", "Not using enough", "Found alternative", "Missing features", "Poor customer support", "Technical issues", "Company closed", "Temporary pause", "Switching plans", "Budget cuts"] + renewal_types: ["Auto-renew", "Manual renewal", "No renewal"] diff --git a/src/test/java/org/thejavaguy/javafaker/SubscriptionTest.java b/src/test/java/org/thejavaguy/javafaker/SubscriptionTest.java new file mode 100644 index 0000000..09a1892 --- /dev/null +++ b/src/test/java/org/thejavaguy/javafaker/SubscriptionTest.java @@ -0,0 +1,205 @@ +package org.thejavaguy.javafaker; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.time.LocalDate; +import org.junit.jupiter.api.Test; + +public class SubscriptionTest extends AbstractFakerTest { + + @Test + void planName() { + assertThat(faker.subscription().planName()).isNotBlank(); + } + + @Test + void status() { + String status = faker.subscription().status(); + assertThat(status).isNotBlank(); + assertThat(status) + .isIn( + "active", + "inactive", + "canceled", + "expired", + "past_due", + "trialing", + "paused", + "pending", + "suspended"); + } + + @Test + void paymentMethod() { + assertThat(faker.subscription().paymentMethod()).isNotBlank(); + } + + @Test + void billingCycle() { + String cycle = faker.subscription().billingCycle(); + assertThat(cycle).isNotBlank(); + assertThat(cycle) + .isIn( + "monthly", + "quarterly", + "semi-annually", + "annually", + "bi-annually", + "weekly", + "bi-weekly"); + } + + @Test + void paymentTerm() { + assertThat(faker.subscription().paymentTerm()).isNotBlank(); + } + + @Test + void currency() { + String currency = faker.subscription().currency(); + assertThat(currency).matches("[A-Z]{3}"); + } + + @Test + void feature() { + assertThat(faker.subscription().feature()).isNotBlank(); + } + + @Test + void subscriptionType() { + assertThat(faker.subscription().subscriptionType()).isNotBlank(); + } + + @Test + void cancellationReason() { + assertThat(faker.subscription().cancellationReason()).isNotBlank(); + } + + @Test + void renewalType() { + assertThat(faker.subscription().renewalType()).isNotBlank(); + } + + @Test + void subscriptionId() { + String id = faker.subscription().subscriptionId(); + assertThat(id).matches("sub_[a-z0-9]{16}"); + } + + @Test + void customerId() { + String id = faker.subscription().customerId(); + assertThat(id).matches("cus_[a-z0-9]{14}"); + } + + @Test + void invoiceId() { + String id = faker.subscription().invoiceId(); + assertThat(id).matches("inv_[a-z0-9]{14}"); + } + + @Test + void price() { + BigDecimal price = faker.subscription().price(); + assertThat(price).isGreaterThanOrEqualTo(BigDecimal.valueOf(5.00)); + assertThat(price).isLessThanOrEqualTo(BigDecimal.valueOf(500.00)); + assertThat(price.scale()).isEqualTo(2); + } + + @Test + void priceWithRange() { + BigDecimal price = faker.subscription().price(10.00, 50.00); + assertThat(price).isGreaterThanOrEqualTo(BigDecimal.valueOf(10.00)); + assertThat(price).isLessThanOrEqualTo(BigDecimal.valueOf(50.00)); + } + + @Test + void priceFormatted() { + String price = faker.subscription().priceFormatted(); + assertThat(price).matches("\\$\\d+\\.\\d{2}"); + } + + @Test + void priceFormattedWithCurrency() { + String price = faker.subscription().priceFormatted("€"); + assertThat(price).matches("€\\d+\\.\\d{2}"); + } + + @Test + void startDate() { + LocalDate date = faker.subscription().startDate(); + assertThat(date).isBeforeOrEqualTo(LocalDate.now()); + assertThat(date).isAfterOrEqualTo(LocalDate.now().minusYears(1)); + } + + @Test + void startDateFormatted() { + String date = faker.subscription().startDate("yyyy-MM-dd"); + assertThat(date).matches("\\d{4}-\\d{2}-\\d{2}"); + } + + @Test + void endDate() { + LocalDate date = faker.subscription().endDate(); + assertThat(date).isAfter(LocalDate.now()); + } + + @Test + void nextBillingDate() { + LocalDate date = faker.subscription().nextBillingDate(); + assertThat(date).isAfter(LocalDate.now()); + assertThat(date).isBeforeOrEqualTo(LocalDate.now().plusDays(30)); + } + + @Test + void trialEndDate() { + LocalDate date = faker.subscription().trialEndDate(); + assertThat(date).isAfter(LocalDate.now().plusDays(6)); + assertThat(date).isBeforeOrEqualTo(LocalDate.now().plusDays(31)); + } + + @Test + void trialPeriodDays() { + int days = faker.subscription().trialPeriodDays(); + assertThat(days).isIn(7, 14, 30); + } + + @Test + void discountPercent() { + int discount = faker.subscription().discountPercent(); + assertThat(discount).isBetween(5, 50); + } + + @Test + void couponCode() { + String code = faker.subscription().couponCode(); + assertThat(code).matches("[A-Z]+\\d+"); + } + + @Test + void userCount() { + int count = faker.subscription().userCount(); + assertThat(count).isBetween(1, 100); + } + + @Test + void userCountWithRange() { + int count = faker.subscription().userCount(5, 10); + assertThat(count).isBetween(5, 10); + } + + @Test + void autoRenew() { + boolean autoRenew = faker.subscription().autoRenew(); + assertThat(autoRenew).isIn(true, false); + } + + @Test + void planDescription() { + String desc = faker.subscription().planDescription(); + assertThat(desc).isNotBlank(); + assertThat(desc).contains("("); + assertThat(desc).contains(")"); + } +}