singularity

用户问题

func TestGetCampaign(t *testing.T) { svc := setupTest(t) ctx := withTestUser(context.Background())

// Create test campaign
campaign := &models.Campaign{
	Title:       "Test Campaign",
	Summary:     "Test Summary",
	Description: "Test Description",
}
created, err := svc.CreateCampaign(ctx, campaign)
assert.NoError(t, err)

// Get campaign
retrieved, err := svc.GetCampaign(ctx, created.Id)
assert.NoError(t, err)
assert.Equal(t, created.Id, retrieved.Id)
assert.Equal(t, campaign.Title, retrieved.Title) }

type Campaign struct { Id string json:”id” Title string json:”title” Summary string json:”summary” Description string json:”description” Status string json:”status” Tags []Tag json:”tags” FeaturedImageKey string json:”featuredImageKey” CreatedAt string json:”createdAt” UpdatedAt string json:”updatedAt” Slug string json:”slug” Url string json:”url” ImageUrl string json:”imageUrl” Actions int64 json:”actions” MaxActions int64 json:”maxActions” ExtraSignatures int64 json:”extraSignatures” Featured bool json:”featured” RestartOnVisit bool json:”restartOnVisit” CreatedBy string json:”createdBy” Campaigner string json:”campaigner” Stages []CampaignJourneyStage json:”stages” AccessId string json:”accessId,omitempty” InterestAreas []string json:”interestAreas,omitempty” VariantFlow string json:”variantFlow” ActiveABTestID string json:”ActiveABTestID,omitempty” }

test is failing with — FAIL: TestGetCampaign (0.01s) panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x186711d]

goroutine 14 [running]: testing.tRunner.func1.2({0x1b46700, 0x34cfa20}) /usr/local/go/src/testing/testing.go:1632 +0x230 testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1635 +0x35e panic({0x1b46700?, 0x34cfa20?}) /usr/local/go/src/runtime/panic.go:785 +0x132 because/internal/modules/campaigns/service/management.(managementService).GetCampaign(0xc000503b60, {0x25290c0, 0xc000503b90}, {0xc0004a5950, 0x24}) /home/suraj/demtech/because-backend/internal/modules/campaigns/service/management/campaign.go:224 +0xf1d because/internal/modules/campaigns/service/management.TestGetCampaign(0xc00015eea0) /home/suraj/demtech/because-backend/internal/modules/campaigns/service/management/management_test.go:67 +0x165 testing.tRunner(0xc00015eea0, 0x2220fe8) /usr/local/go/src/testing/testing.go:1690 +0xf4 created by testing.(T).Run in goroutine 1 /usr/local/go/src/testing/testing.go:1743 +0x390 exit status 2 FAIL because/internal/modules/campaigns/service/management 0.065s

ChatGPT

This panic is a classic nil dereference—likely from an assumption that something non-nil is being accessed, but it isn’t. The stack trace points directly to this line:

because/internal/modules/campaigns/service/management.(*managementService).GetCampaign(...) +0xf1d

Most probable cause

You’re probably accessing a field or method on a nil value in GetCampaign.

Quick diagnosis steps:

  1. Check svc.GetCampaign (around line 224 of campaign.go). Look for something like:
svc.repo.FindByID(...)  // or anything being called on a nil field
  1. Is managementService partially initialized?
if m.repo == nil {
    panic("repo is nil in GetCampaign")
}

Fix direction

If the issue is a nil repo or similar, fix setupTest(t) to initialize it:

func setupTest(t *testing.T) *managementService {
    client := enttest.Open(t, "sqlite3", "file:mem?mode=memory&cache=shared&_fk=1")
    logger := zap.NewNop()  // or whatever logger you're using
    repo := NewRepo(client)

    return &managementService{
        client: client,
        repo:   repo,
        logger: logger,
    }
}

Let me know what the fields of managementService are and what setupTest is doing—I’ll pinpoint it fast.

用户问题

i had added this line in GetCampaign

activeAbtest := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).FirstX(ctx)

test was passing before i added this goent query which might return nil

ChatGPT

Got it—this is your smoking gun:

activeAbtest := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).FirstX(ctx)

Problem

.FirstX(ctx) panics if no result is found. That’s its job. This is where your nil dereference crash is coming from.

Fix

Swap to .First(ctx) and handle the error:

abtest, err := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).
	First(ctx)

if err != nil && !ent.IsNotFound(err) {
	return nil, err  // or log + return internal error
}

// abtest may be nil if not found

Best Practice

Use .FirstX only when you’re guaranteed a result. Otherwise, always check the error from .First.

Fix that and the test should pass again.

Want help making this idiomatic or defensive for prod use?

用户问题

it says first only returns one value

ChatGPT

Yup—this is a quirk of GoEnt.

The actual method signature of .First(ctx):

func (q *AbTestQuery) First(ctx context.Context) (*AbTest, error)

So you should do:

activeAbtest, err := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).
	First(ctx)

if err != nil && !ent.IsNotFound(err) {
	return nil, err
}

// activeAbtest will be nil if not found

⚠️ Key point: don’t re-use the variable name abtest for the type/package—it will shadow. Safer to call the result activeAbtest as you did originally.

Let me know if you want a pattern to handle optional single results more cleanly—especially if you want to hide ent internals behind your repo/service layer.

用户问题

okay test is still failing

ChatGPT

Alright—if .First(ctx) is in place and the panic still happens, something else is nil before or after that call.

Let’s isolate this quickly.

🔍 Add this right before the query:

if campaign_obj == nil {
    panic("campaign_obj is nil before QueryAbtests")
}

And log the ID you’re passing:

fmt.Printf("Querying AbTests for campaign ID: %s\n", cid)

Then: Change .First(ctx) to a safe debug version temporarily

activeAbtest, err := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).
	First(ctx)

if err != nil {
    if ent.IsNotFound(err) {
        fmt.Println("No active AB test found")
    } else {
        panic(fmt.Sprintf("failed to query abtest: %v", err))
    }
}

Also: Check line 224 in campaign.go

Where exactly is the panic coming from? Might be you’re using activeAbtest.ID or some field without checking for nil after .First.

Likely fix:

var activeABTestID string
if activeAbtest != nil {
    activeABTestID = activeAbtest.ID
}

campaign.ActiveABTestID = activeABTestID

You probably went straight to:

campaign.ActiveABTestID = activeAbtest.ID  // <== this will panic if nil

That match your line 224? If not, share that line—I’ll lock on target.

用户问题

yeah this is as follows

ChatGPT

Perfect—go ahead and drop that snippet of the code around line 224 so I can confirm exactly where it’s blowing up.

Also useful:

With that, I’ll zero in and give you a tight patch.

用户问题

parse uuid as string

ChatGPT

If you’re trying to safely assign a UUID to a string field (like campaign.ActiveABTestID), here’s the clean way to do it:

Assuming activeAbtest.ID is of type uuid.UUID (from github.com/google/uuid or similar):

✅ Safe assign:

if activeAbtest != nil {
	campaign.ActiveABTestID = activeAbtest.ID.String()
}

This avoids the panic from nil and converts the UUID to a string.

Let me know if your UUID type is different or you want to validate the format too.

用户问题

here’s the whole function now func (svc managementService) GetCampaign(ctx context.Context, campaignId string) (models.Campaign, error) {

uid, err := bootstrap.GetUserIdFromContext(ctx)
if err != nil {
	return nil, errors.New("no user id in context")
}

cid, err := uuid.Parse(campaignId)
if err != nil {
	return nil, err
}
campaign_obj, err := svc.client.Campaign.Query().Where(campaign.IDEQ(cid)).WithAbtests().Only(ctx)
if err != nil {
	svc.log.Error("NO CAMPAIGNS", "error", err)
	return nil, err
}

//NOTE: design assumption - a campaign cannot have more than one active a/b test at a time
activeAbtest, errActiveTest := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).First(ctx)

if errActiveTest != nil || ent.IsNotFound(err) {
	return nil, err
}

tags, err := campaign_obj.QueryTags().All(ctx)
if err != nil {
	return nil, err
}

out_tags := []*models.Tag{}

for _, tag := range tags {
	out_tags = append(out_tags, &models.Tag{
		Id:    tag.ID,
		Name:  tag.Name,
		Color: tag.Color,
	})
}

slug := ""
if campaign_obj.Slug != nil {
	slug = *campaign_obj.Slug
}

campaignFeaturedKey := ""
if campaign_obj.FeaturedImageKey != nil {
	campaignFeaturedKey = *campaign_obj.FeaturedImageKey
}

out_campaign := &models.Campaign{
	Id:               campaign_obj.ID.String(),
	Title:            campaign_obj.Title,
	Summary:          campaign_obj.Summary,
	Description:      campaign_obj.Description,
	Tags:             out_tags,
	Slug:             slug,
	Status:           predicateStatus(campaign_obj),
	CreatedAt:        campaign_obj.CreatedAt.String(),
	UpdatedAt:        campaign_obj.UpdatedAt.String(),
	FeaturedImageKey: campaignFeaturedKey,
	ExtraSignatures:  campaign_obj.ExtraSignatures,
	RestartOnVisit:   campaign_obj.RestartOnVisit,
	Stages:           campaign_obj.Stages,
	CreatedBy:        campaign_obj.CreatedBy,
	Campaigner:       campaign_obj.Campaigner,
	AccessId:         campaign_obj.AccessID,
	InterestAreas:    campaign_obj.InterestAreas,
	VariantFlow:      campaign_obj.VariantFlow,
	Featured:         campaign_obj.Featured,
	ActiveABTestID:   activeAbtest.ID.String(),
}

if campaign_obj.FeaturedImageKey != nil {
	out_campaign.FeaturedImageKey = *campaign_obj.FeaturedImageKey
} else {
	out_campaign.FeaturedImageKey = ""
}

err = svc.actionHistory.RecordEvent(ctx, "campaign", "read", makeCampaignEvent(campaign_obj, &models.User{
	Id: uid.String(),
}))
if err != nil {
	svc.log.Error("Failed to record event campaign.create", "error", err)
}

return out_campaign, nil } test is still failing # ChatGPT This is the issue:
ActiveABTestID: activeAbtest.ID.String(),

If activeAbtest is nil, this line panics, regardless of the earlier check.

Also this check is wrong:

if errActiveTest != nil || ent.IsNotFound(err) {
	return nil, err
}

You’re checking ent.IsNotFound(err) instead of ent.IsNotFound(errActiveTest).

✅ Fix: Defensive handling

Replace this block:

activeAbtest, errActiveTest := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).First(ctx)

if errActiveTest != nil || ent.IsNotFound(err) {
	return nil, err
}

With this:

var activeABTestID string
activeAbtest, errActiveTest := campaign_obj.QueryAbtests().
	Where(abtest.CampaignIDEQ(cid), abtest.StatusEQ("active")).First(ctx)

if errActiveTest != nil && !ent.IsNotFound(errActiveTest) {
	return nil, errActiveTest
}

if activeAbtest != nil {
	activeABTestID = activeAbtest.ID.String()
}

Then later:

ActiveABTestID: activeABTestID,

Summary

Apply that fix and the test should pass cleanly. Want a short test for this edge case too?

用户问题

neovim return to splash scren

ChatGPT

To return to the Neovim splash screen (the default “welcome” screen), do one of the following:

If no files are open:

Run:

:enew | only

This creates a new empty buffer and closes all others. If your init.lua or init.vim has set shortmess-=I, the splash screen (start screen) will appear.

Force it manually with alpha-nvim, dashboard-nvim, etc.:

If you’re using a plugin like alpha-nvim or dashboard-nvim, they usually expose a command:

For alpha-nvim:

:Alpha

For dashboard-nvim:

:Dashboard

🔍 Want auto-splash when all buffers are closed?

Install something like this in Lua:

vim.api.nvim_create_autocmd("BufDelete", {
  callback = function()
    if #vim.fn.getbufinfo({ buflisted = 1 }) == 0 then
      vim.cmd("Alpha")  -- or :Dashboard
    end
  end
})

Let me know what plugin you’re using for the splash, if any. I can tailor this to your setup.