M analytics/routes_test.go => analytics/routes_test.go +53 -1
@@ 1,6 1,7 @@
package analytics_test
import (
+ "fmt"
"links/analytics"
"links/cmd"
"links/models"
@@ 11,13 12,64 @@ import (
"time"
"git.sr.ht/~emersion/gqlclient"
+ "github.com/jarcoal/httpmock"
+ "github.com/labstack/echo/v4"
"github.com/stretchr/testify/require"
oauth2 "netlandish.com/x/gobwebs-oauth2"
"netlandish.com/x/gobwebs/crypto"
"netlandish.com/x/gobwebs/server"
)
-func TestHandlers(t *testing.T) {
+func TestHandler(t *testing.T) {
+ c := require.New(t)
+ srv, e := cmd.NewWebTestServer(t)
+ cmd.RunMigrations(t, srv.DB)
+ analyticsService := analytics.NewService(e.Group("/analytics"))
+ user := cmd.NewTestUser(1, false, false, true, true)
+ defer srv.Shutdown()
+ go srv.Run()
+ dbCtx := cmd.NewDBContext(srv.DB, "America/Managua")
+
+ httpmock.Activate()
+ defer httpmock.DeactivateAndReset()
+ jsonResponse, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/analytics.json"))
+ c.NoError(err)
+ httpmock.RegisterResponder("POST", "http://127.0.0.1:8080/query", jsonResponse)
+
+ t.Run("checkout success", func(t *testing.T) {
+ short := &models.LinkShort{
+ Title: "Testing short",
+ URL: "http://testing.com",
+ ShortCode: "abc",
+ OrgID: 1,
+ UserID: int(user.ID),
+ DomainID: 1,
+ }
+ err := short.Store(dbCtx)
+ c.NoError(err)
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+
+ // Add fake analytics entries
+ err = analytics.AddAnalytics(dbCtx, req, short.ID, analytics.LinkShortAnalyticsFilter, "")
+ c.NoError(err)
+ request := httptest.NewRequest(http.MethodGet, "/shorts/1", nil)
+ request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: user,
+ }
+ ctx.SetPath("/:slug/analytics/:type/:id")
+ ctx.SetParamNames("slug", "type", "id")
+ ctx.SetParamValues("personal-org", "shorts", fmt.Sprint(short.ID))
+ err = cmd.MakeRequest(srv, analyticsService.Detail, ctx)
+ c.NoError(err)
+ c.Equal(http.StatusOK, recorder.Code)
+ })
+}
+
+func TestAPI(t *testing.T) {
c := require.New(t)
srv, e, entropy := cmd.NewAPITestServer(t)
cmd.RunMigrations(t, srv.DB)
A analytics/samples/analytics.json => analytics/samples/analytics.json +68 -0
@@ 0,0 1,68 @@
+{
+ "data": {
+ "analytics": {
+ "title": "New Listst",
+ "clicksOverTime": [
+ {
+ "key": "2024-01-21",
+ "val": 10
+ },
+ {
+ "key": "2024-01-28",
+ "val": 10
+ },
+ {
+ "key": "2024-02-04",
+ "val": 10
+ },
+ {
+ "key": "2024-02-11",
+ "val": 10
+ },
+ {
+ "key": "2024-01-14",
+ "val": 10
+ }
+ ],
+ "listingLink": [
+ {
+ "id": 12,
+ "title": "Title",
+ "clicks": 12
+ }
+ ],
+ "qrList": [
+ {
+ "id": 12,
+ "title": "Title",
+ "clicks": 12
+ }
+ ],
+ "clicksCountry": [
+ {
+ "key": "Country",
+ "val": 1000
+ }
+ ],
+ "clicksCity": [
+ {
+ "key": "City",
+ "val": 1000
+ }
+
+ ],
+ "clicksDevice": [
+ {
+ "key": "Device",
+ "val": 1000
+ }
+ ],
+ "clicksReferrer": [
+ {
+ "key": "Referrer",
+ "val": 1000
+ }
+ ]
+ }
+ }
+}
M billing/routes_test.go => billing/routes_test.go +40 -0
@@ 30,6 30,46 @@ func TestHandlers(t *testing.T) {
go srv.Run()
dbCtx := cmd.NewDBContext(srv.DB, "America/Managua")
+ t.Run("checkout success", func(t *testing.T) {
+ httpmock.Activate()
+ defer httpmock.DeactivateAndReset()
+ checkoutSession, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/get_checkout_session.json"))
+ c.NoError(err)
+ httpmock.RegisterResponder("GET", "https://api.stripe.com/v1/checkout/sessions/sid", checkoutSession)
+
+ invoiceList, err := httpmock.NewJsonResponder(http.StatusOK, httpmock.File("samples/invoice_list.json"))
+ c.NoError(err)
+ httpmock.RegisterResponder("GET", "https://api.stripe.com/v1/invoices", invoiceList)
+
+ request := httptest.NewRequest(http.MethodGet, "/sucess?session_id=sid", nil)
+ request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: user,
+ }
+ ctx.SetPath("/success")
+ err = cmd.MakeRequest(srv, billingService.CheckoutSuccess, ctx)
+ c.NoError(err)
+ c.Equal(http.StatusOK, recorder.Code)
+ })
+
+ t.Run("checkou cancel", func(t *testing.T) {
+ request := httptest.NewRequest(http.MethodGet, "/cancel", nil)
+ request.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
+ recorder := httptest.NewRecorder()
+ ctx := &server.Context{
+ Server: srv,
+ Context: e.NewContext(request, recorder),
+ User: user,
+ }
+ ctx.SetPath("/cancel")
+ err := cmd.MakeRequest(srv, billingService.CheckoutCancel, ctx)
+ c.NoError(err)
+ c.Equal(http.StatusOK, recorder.Code)
+ })
+
t.Run("create subscription", func(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
A billing/samples/get_checkout_session.json => billing/samples/get_checkout_session.json +74 -0
@@ 0,0 1,74 @@
+{
+ "id": "cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u",
+ "object": "checkout.session",
+ "after_expiration": null,
+ "allow_promotion_codes": null,
+ "amount_subtotal": 2198,
+ "amount_total": 2198,
+ "automatic_tax": {
+ "enabled": false,
+ "liability": null,
+ "status": null
+ },
+ "billing_address_collection": null,
+ "cancel_url": null,
+ "client_reference_id": null,
+ "consent": null,
+ "consent_collection": null,
+ "created": 1679600215,
+ "currency": "usd",
+ "custom_fields": [],
+ "custom_text": {
+ "shipping_address": null,
+ "submit": null
+ },
+ "customer": null,
+ "customer_creation": "if_required",
+ "customer_details": null,
+ "customer_email": null,
+ "expires_at": 1679686615,
+ "invoice": null,
+ "invoice_creation": {
+ "enabled": false,
+ "invoice_data": {
+ "account_tax_ids": null,
+ "custom_fields": null,
+ "description": null,
+ "footer": null,
+ "issuer": null,
+ "metadata": {},
+ "rendering_options": null
+ }
+ },
+ "livemode": false,
+ "locale": null,
+ "metadata": {},
+ "mode": "payment",
+ "payment_intent": null,
+ "payment_link": null,
+ "payment_method_collection": "always",
+ "payment_method_options": {},
+ "payment_method_types": [
+ "card"
+ ],
+ "payment_status": "unpaid",
+ "phone_number_collection": {
+ "enabled": false
+ },
+ "recovered_from": null,
+ "setup_intent": null,
+ "shipping_address_collection": null,
+ "shipping_cost": null,
+ "shipping_details": null,
+ "shipping_options": [],
+ "status": "open",
+ "submit_type": null,
+ "subscription": "12332",
+ "success_url": "https://example.com/success",
+ "total_details": {
+ "amount_discount": 0,
+ "amount_shipping": 0,
+ "amount_tax": 0
+ },
+ "url": "https://checkout.stripe.com/c/pay/cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u#fidkdWxOYHwnPyd1blpxYHZxWjA0SDdPUW5JbmFMck1wMmx9N2BLZjFEfGRUNWhqTmJ%2FM2F8bUA2SDRySkFdUV81T1BSV0YxcWJcTUJcYW5rSzN3dzBLPUE0TzRKTTxzNFBjPWZEX1NKSkxpNTVjRjN8VHE0YicpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl"
+}
A billing/samples/invoice_list.json => billing/samples/invoice_list.json +108 -0
@@ 0,0 1,108 @@
+{
+ "object": "list",
+ "url": "/v1/invoices",
+ "has_more": false,
+ "data": [
+ {
+ "id": "in_1MtHbELkdIwHu7ixl4OzzPMv",
+ "object": "invoice",
+ "account_country": "US",
+ "account_name": "Stripe Docs",
+ "account_tax_ids": null,
+ "amount_due": 0,
+ "amount_paid": 0,
+ "amount_remaining": 0,
+ "amount_shipping": 0,
+ "application": null,
+ "application_fee_amount": null,
+ "attempt_count": 0,
+ "attempted": false,
+ "auto_advance": false,
+ "automatic_tax": {
+ "enabled": false,
+ "liability": null,
+ "status": null
+ },
+ "billing_reason": "manual",
+ "charge": null,
+ "collection_method": "charge_automatically",
+ "created": 1680644467,
+ "currency": "usd",
+ "custom_fields": null,
+ "customer": "cus_NeZwdNtLEOXuvB",
+ "customer_address": null,
+ "customer_email": "jennyrosen@example.com",
+ "customer_name": "Jenny Rosen",
+ "customer_phone": null,
+ "customer_shipping": null,
+ "customer_tax_exempt": "none",
+ "customer_tax_ids": [],
+ "default_payment_method": null,
+ "default_source": null,
+ "default_tax_rates": [],
+ "description": null,
+ "discount": null,
+ "discounts": [],
+ "due_date": null,
+ "ending_balance": null,
+ "footer": null,
+ "from_invoice": null,
+ "hosted_invoice_url": null,
+ "invoice_pdf": null,
+ "issuer": {
+ "type": "self"
+ },
+ "last_finalization_error": null,
+ "latest_revision": null,
+ "lines": {
+ "object": "list",
+ "data": [],
+ "has_more": false,
+ "total_count": 0,
+ "url": "/v1/invoices/in_1MtHbELkdIwHu7ixl4OzzPMv/lines"
+ },
+ "livemode": false,
+ "metadata": {},
+ "next_payment_attempt": null,
+ "number": null,
+ "on_behalf_of": null,
+ "paid": false,
+ "paid_out_of_band": false,
+ "payment_intent": null,
+ "payment_settings": {
+ "default_mandate": null,
+ "payment_method_options": null,
+ "payment_method_types": null
+ },
+ "period_end": 1680644467,
+ "period_start": 1680644467,
+ "post_payment_credit_notes_amount": 0,
+ "pre_payment_credit_notes_amount": 0,
+ "quote": null,
+ "receipt_number": null,
+ "rendering_options": null,
+ "shipping_cost": null,
+ "shipping_details": null,
+ "starting_balance": 0,
+ "statement_descriptor": null,
+ "status": "draft",
+ "status_transitions": {
+ "finalized_at": null,
+ "marked_uncollectible_at": null,
+ "paid_at": null,
+ "voided_at": null
+ },
+ "subscription": null,
+ "subtotal": 0,
+ "subtotal_excluding_tax": 0,
+ "tax": null,
+ "test_clock": null,
+ "total": 0,
+ "total_discount_amounts": [],
+ "total_excluding_tax": 0,
+ "total_tax_amounts": [],
+ "transfer_data": null,
+ "webhooks_delivered_at": 1680644467
+ }
+ ]
+}