mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-22 00:03:21 +00:00
* feat: take AI off the free plan, zeroing the free allowance and gating the writing assistant to paid like the inbox agent already was, because a trial that can only ever be told it has no credits reads as a broken feature rather than a locked one * feat: state in the down migration which case does not round-trip, a free plan already at zero before 000148 ran, since the up migration recorded no prior value and a zero it did not write cannot be told from one it did
75 lines
2.3 KiB
Go
75 lines
2.3 KiB
Go
package feature
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
"github.com/warmbly/warmbly/internal/repository"
|
|
)
|
|
|
|
// Only the one read the AI gates make is implemented; anything else panics,
|
|
// which is the point.
|
|
type stubSubs struct {
|
|
repository.SubscriptionRepository
|
|
sub *models.Subscription
|
|
}
|
|
|
|
func (s stubSubs) GetByOrganizationID(context.Context, uuid.UUID) (*models.Subscription, error) {
|
|
return s.sub, nil
|
|
}
|
|
|
|
func trialSub() *models.Subscription {
|
|
future := time.Now().Add(14 * 24 * time.Hour)
|
|
return &models.Subscription{
|
|
PlanID: uuid.MustParse("00000000-0000-0000-0000-000000000001"),
|
|
Status: models.SubscriptionStatusTrialing,
|
|
FreeTrialEndsAt: &future,
|
|
}
|
|
}
|
|
|
|
// The free plan carries no credit allowance, so the assistant must be locked
|
|
// rather than shown. Offering it to someone who can only ever be told they
|
|
// have no credits reads as a broken feature, not a paid one.
|
|
func TestWritingAssistantIsPaidOnly(t *testing.T) {
|
|
g := &featureGateService{subRepo: stubSubs{sub: trialSub()}}
|
|
|
|
ok, xerr := g.CanUseWritingAssistant(context.Background(), uuid.New())
|
|
if xerr != nil {
|
|
t.Fatalf("unexpected error: %v", xerr)
|
|
}
|
|
if ok {
|
|
t.Error("a free trial must not be offered the writing assistant")
|
|
}
|
|
}
|
|
|
|
func TestWritingAssistantAndInboxAgentAgree(t *testing.T) {
|
|
ctx := context.Background()
|
|
org := uuid.New()
|
|
g := &featureGateService{subRepo: stubSubs{sub: trialSub()}}
|
|
|
|
wa, _ := g.CanUseWritingAssistant(ctx, org)
|
|
ia, _ := g.CanUseInboxAgent(ctx, org)
|
|
if wa != ia {
|
|
t.Errorf("both AI features should gate the same way on a free trial: assistant=%v agent=%v", wa, ia)
|
|
}
|
|
}
|
|
|
|
// No subscription at all is still a refusal, not a panic.
|
|
func TestAIGatesRefuseWithNoSubscription(t *testing.T) {
|
|
g := &featureGateService{subRepo: stubSubs{sub: nil}}
|
|
if ok, _ := g.CanUseWritingAssistant(context.Background(), uuid.New()); ok {
|
|
t.Error("no subscription must not unlock the assistant")
|
|
}
|
|
}
|
|
|
|
// A self-hosted instance has no payment provider, so every gate stays open.
|
|
func TestSelfHostKeepsAIOpen(t *testing.T) {
|
|
g := &featureGateService{selfHost: true}
|
|
if ok, _ := g.CanUseWritingAssistant(context.Background(), uuid.New()); !ok {
|
|
t.Error("self-host must keep the assistant available")
|
|
}
|
|
}
|