Ctrl K
ComunidadGuías…

internal/repository/publish_group_postgres_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Integration tests for publish_group persistence: the column is written by
// both insert paths (plain publish, rollout publish) and surfaced by the
// branch listing. Same TEST_DATABASE_URL gating as the rollout store tests.
package repository_test

import (
	"context"
	"strconv"
	"testing"
	"xprem/internal/types"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// checkedUpdate publishes and checks one update, stamping a stored uuid so the
// listing resolves it without reaching for bucket metadata.
func (f *rolloutFixture) checkedUpdate(t *testing.T, updateId int64, platform types.Platform, publishGroup *string) {
	t.Helper()
	ctx := context.Background()
	created, err := f.updates.CreateUpdate(ctx, f.appId, updateId, rolloutTestDefaultBranch, rolloutTestRuntime, platform, "abc123", "", publishGroup)
	require.NoError(t, err)
	require.NoError(t, f.updates.MarkUpdateAsChecked(ctx, *created))
	require.NoError(t, f.updates.StoreUpdateUUIDInMetadata(ctx, *created, uuid.NewString()))
}

func TestPublishGroupPersistencePostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()

	// One publish run: two platform rows sharing the CLI-minted group. A row
	// published without one (older CLI) stays ungrouped.
	publishGroup := uuid.NewString()
	fixture.checkedUpdate(t, 100, "ios", &publishGroup)
	fixture.checkedUpdate(t, 200, "android", &publishGroup)
	fixture.checkedUpdate(t, 300, "ios", nil)

	// Rollbacks are branch-level operations, never grouped: the marker row
	// must list with no publish group.
	rollback, err := fixture.updates.CreateRollback(ctx, fixture.appId, 400, rolloutTestDefaultBranch, rolloutTestRuntime, "android", "abc123", "")
	require.NoError(t, err)
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *rollback))

	// A rollout publish goes through the dedicated insert; group stored the same.
	rolloutGroup := uuid.NewString()
	rolloutUpdate, err := fixture.updates.CreateUpdateWithRollout(ctx, fixture.appId, 500, rolloutTestDefaultBranch, rolloutTestRuntime, "ios", "abc123", "", 25, &rolloutGroup)
	require.NoError(t, err)
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *rolloutUpdate))
	require.NoError(t, fixture.updates.StoreUpdateUUIDInMetadata(ctx, *rolloutUpdate, uuid.NewString()))

	page, err := fixture.updates.GetUpdatesByRunTimeVersionAndBranchName(ctx, fixture.appId, rolloutTestRuntime, rolloutTestDefaultBranch, nil, 100)
	require.NoError(t, err)
	items := page.Items
	require.Len(t, items, 5)

	groupsById := map[string]*string{}
	for _, item := range items {
		groupsById[item.UpdateId] = item.PublishGroup
	}
	require.NotNil(t, groupsById["100"])
	require.NotNil(t, groupsById["200"])
	assert.Equal(t, publishGroup, *groupsById["100"])
	assert.Equal(t, publishGroup, *groupsById["200"])
	assert.Nil(t, groupsById["300"])
	assert.Nil(t, groupsById["400"])
	require.NotNil(t, groupsById["500"])
	assert.Equal(t, rolloutGroup, *groupsById["500"])
}

func TestGetUpdatesByPublishGroupPostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()

	group := uuid.NewString()
	fixture.checkedUpdate(t, 100, "ios", &group)
	fixture.checkedUpdate(t, 200, "android", &group)
	// An unchecked member is an unfinished upload: invisible to group operations.
	_, err := fixture.updates.CreateUpdate(ctx, fixture.appId, 300, rolloutTestDefaultBranch, rolloutTestRuntime, "ios", "abc123", "", &group)
	require.NoError(t, err)
	// Another group on the same branch stays out of the result.
	otherGroup := uuid.NewString()
	fixture.checkedUpdate(t, 400, "ios", &otherGroup)

	members, err := fixture.updates.GetUpdatesByPublishGroup(ctx, fixture.appId, rolloutTestDefaultBranch, rolloutTestRuntime, group)
	require.NoError(t, err)
	require.Len(t, members, 2)
	assert.Equal(t, "100", members[0].UpdateId)
	assert.Equal(t, types.PlatformIOS, members[0].Platform)
	assert.Equal(t, "abc123", members[0].CommitHash)
	assert.Equal(t, "200", members[1].UpdateId)
	assert.Equal(t, types.PlatformAndroid, members[1].Platform)

	none, err := fixture.updates.GetUpdatesByPublishGroup(ctx, fixture.appId, rolloutTestDefaultBranch, rolloutTestRuntime, uuid.NewString())
	require.NoError(t, err)
	assert.Empty(t, none)
}

func TestGetPublishGroupsPagePostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()

	groups := []string{uuid.NewString(), uuid.NewString(), uuid.NewString()}
	for index, group := range groups {
		baseID := int64((index + 1) * 100)
		fixture.checkedUpdate(t, baseID+1, "ios", &group)
		fixture.checkedUpdate(t, baseID+2, "android", &group)
	}
	// Neither legacy ungrouped updates nor unfinished groups are list entries.
	fixture.checkedUpdate(t, 450, "android", nil)
	unfinishedGroup := uuid.NewString()
	_, err := fixture.updates.CreateUpdate(ctx, fixture.appId, 500, rolloutTestDefaultBranch, rolloutTestRuntime, "ios", "abc123", "", &unfinishedGroup)
	require.NoError(t, err)

	firstPage, err := fixture.updates.GetPublishGroupsPage(ctx, fixture.appId, rolloutTestDefaultBranch, rolloutTestRuntime, nil, 2)
	require.NoError(t, err)
	require.Len(t, firstPage.Items, 2)
	require.NotNil(t, firstPage.NextCursor)
	assert.Equal(t, groups[2], firstPage.Items[0].PublishGroup)
	assert.Equal(t, []string{"ios", "android"}, firstPage.Items[0].Platforms)
	assert.Len(t, firstPage.Items[0].Updates, 2)
	assert.Equal(t, groups[1], firstPage.Items[1].PublishGroup)

	cursor, err := strconv.ParseInt(*firstPage.NextCursor, 10, 64)
	require.NoError(t, err)
	secondPage, err := fixture.updates.GetPublishGroupsPage(ctx, fixture.appId, rolloutTestDefaultBranch, rolloutTestRuntime, &cursor, 2)
	require.NoError(t, err)
	require.Len(t, secondPage.Items, 1)
	assert.Nil(t, secondPage.NextCursor)
	assert.Equal(t, groups[0], secondPage.Items[0].PublishGroup)
}

func TestGetUpdateFeedPostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()

	group := uuid.NewString()
	fixture.checkedUpdate(t, 100, "ios", &group)
	fixture.checkedUpdate(t, 200, "android", &group)
	next, err := fixture.updates.CreateUpdate(ctx, fixture.appId, 300, rolloutTestRolloutBranch, rolloutTestRuntime, "ios", "def456", "next branch", nil)
	require.NoError(t, err)
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *next))
	nextUUID := uuid.NewString()
	require.NoError(t, fixture.updates.StoreUpdateUUIDInMetadata(ctx, *next, nextUUID))
	_, err = fixture.updates.CreateUpdate(ctx, fixture.appId, 400, rolloutTestDefaultBranch, rolloutTestRuntime, "ios", "unfinished", "", nil)
	require.NoError(t, err)

	items, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, types.UpdateFeedQuery{Limit: 100})
	require.NoError(t, err)
	require.Len(t, items, 3, "unchecked uploads must stay out of the dashboard feed")

	byID := make(map[string]types.UpdateFeedItem, len(items))
	for _, item := range items {
		byID[item.UpdateId] = item
	}
	assert.Equal(t, rolloutTestDefaultBranch, byID["100"].Branch)
	assert.Equal(t, rolloutTestRuntime, byID["100"].RuntimeVersion)
	require.NotNil(t, byID["100"].PublishGroup)
	assert.Equal(t, group, *byID["100"].PublishGroup)
	assert.Equal(t, rolloutTestRolloutBranch, byID["300"].Branch)
	assert.Equal(t, nextUUID, byID["300"].UpdateUUID)
	assert.Equal(t, "next branch", byID["300"].Message)

	groupItems, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, types.UpdateFeedQuery{
		PublishGroup: group,
		Limit:        100,
	})
	require.NoError(t, err)
	require.Len(t, groupItems, 2)

	firstPage, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, types.UpdateFeedQuery{Limit: 2})
	require.NoError(t, err)
	require.Len(t, firstPage, 2)
	cursorUpdateID, err := strconv.ParseInt(firstPage[1].UpdateId, 10, 64)
	require.NoError(t, err)
	secondPage, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, types.UpdateFeedQuery{
		CursorCreatedAt: &firstPage[1].FeedCreatedAt,
		CursorBranchID:  firstPage[1].BranchID,
		CursorUpdateID:  cursorUpdateID,
		Limit:           2,
	})
	require.NoError(t, err)
	require.Len(t, secondPage, 1)
	assert.NotEqual(t, firstPage[0].UpdateId, secondPage[0].UpdateId)
	assert.NotEqual(t, firstPage[1].UpdateId, secondPage[0].UpdateId)
}

func TestGetLatestUpdateFeedPostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()
	fixture.checkedUpdate(t, 100, types.PlatformIOS, nil)
	fixture.checkedUpdate(t, 200, types.PlatformAndroid, nil)
	rollback, err := fixture.updates.CreateRollback(ctx, fixture.appId, 300, rolloutTestDefaultBranch, rolloutTestRuntime, types.PlatformIOS, "rollback", "back to embedded")
	require.NoError(t, err)
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *rollback))
	// Uploading a newer row must not hide the last checked serving head.
	_, err = fixture.updates.CreateUpdate(ctx, fixture.appId, 400, rolloutTestDefaultBranch, rolloutTestRuntime, types.PlatformIOS, "unfinished", "", nil)
	require.NoError(t, err)
	newRuntime := "2.0.0"
	_, err = fixture.branches.CreateRuntimeVersion(ctx, fixture.appId, newRuntime)
	require.NoError(t, err)
	newer, err := fixture.updates.CreateUpdate(ctx, fixture.appId, 500, rolloutTestDefaultBranch, newRuntime, types.PlatformAndroid, "new-runtime", "", nil)
	require.NoError(t, err)
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *newer))
	require.NoError(t, fixture.updates.StoreUpdateUUIDInMetadata(ctx, *newer, uuid.NewString()))
	fixture.createUpdate(t, rolloutTestRolloutBranch, 300, types.PlatformIOS, true)
	foreign := newRolloutFixture(t)
	foreign.checkedUpdate(t, 300, types.PlatformIOS, nil)

	query := types.UpdateFeedQuery{Branch: rolloutTestDefaultBranch, LatestOnly: true, Limit: 100}
	heads, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, query)
	require.NoError(t, err)
	require.Len(t, heads, 3, "one checked head per runtime and platform, including older runtimes and rollback")
	ids := make([]string, 0, len(heads))
	for _, head := range heads {
		ids = append(ids, head.UpdateId)
		if head.UpdateId == "300" {
			assert.Equal(t, "Rollback to embedded", head.UpdateUUID)
		}
	}
	assert.ElementsMatch(t, []string{"200", "300", "500"}, ids)

	// Applying the cursor after picking each head must never reveal the old
	// version of a scope whose newest row appeared on an earlier page.
	query.Limit = 1
	var pagedIDs []string
	for {
		page, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, query)
		require.NoError(t, err)
		if len(page) == 0 {
			break
		}
		require.Len(t, page, 1)
		pagedIDs = append(pagedIDs, page[0].UpdateId)
		require.LessOrEqual(t, len(pagedIDs), 3, "paging must not repeat a serving head")
		query.CursorCreatedAt = &page[0].FeedCreatedAt
		query.CursorBranchID = page[0].BranchID
		query.CursorUpdateID, err = strconv.ParseInt(page[0].UpdateId, 10, 64)
		require.NoError(t, err)
	}
	assert.ElementsMatch(t, ids, pagedIDs)
	filtered, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, types.UpdateFeedQuery{
		Branch: rolloutTestDefaultBranch, RuntimeVersion: rolloutTestRuntime,
		Platform: types.PlatformIOS, LatestOnly: true, Limit: 100,
	})
	require.NoError(t, err)
	require.Len(t, filtered, 1)
	assert.Equal(t, "300", filtered[0].UpdateId)
}

func TestGetLatestUpdateFeedIncludesActiveControlPostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()
	fixture.checkedUpdate(t, 100, types.PlatformIOS, nil)
	fixture.checkedUpdate(t, 200, types.PlatformAndroid, nil)
	candidate, err := fixture.updates.CreateUpdateWithRollout(ctx, fixture.appId, 300, rolloutTestDefaultBranch, rolloutTestRuntime, types.PlatformIOS, "candidate", "", 10, nil)
	require.NoError(t, err)
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *candidate))
	require.NoError(t, fixture.updates.StoreUpdateUUIDInMetadata(ctx, *candidate, uuid.NewString()))
	query := types.UpdateFeedQuery{Branch: rolloutTestDefaultBranch, Platform: types.PlatformIOS, LatestOnly: true, Limit: 100}
	active, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, query)
	require.NoError(t, err)
	require.Len(t, active, 2, "the active rollout serves both the candidate and its control")
	assert.ElementsMatch(t, []string{"100", "300"}, []string{active[0].UpdateId, active[1].UpdateId})
	_, err = fixture.rollouts.ClearUpdateRollout(ctx, fixture.appId, rolloutTestDefaultBranch, rolloutTestRuntime)
	require.NoError(t, err)
	finished, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, query)
	require.NoError(t, err)
	require.Len(t, finished, 1, "a finished rollout no longer serves the historical control")
	assert.Equal(t, "300", finished[0].UpdateId)
}

// TestPublishGroupRolloutActivationPostgres verifies the second platform of a
// grouped rollout publish still activates even though the first platform's rollout is already active.
func TestPublishGroupRolloutActivationPostgres(t *testing.T) {
	fixture := newRolloutFixture(t)
	ctx := context.Background()

	group := uuid.NewString()
	ios, err := fixture.updates.CreateUpdateWithRollout(ctx, fixture.appId, 600, rolloutTestDefaultBranch, rolloutTestRuntime, "ios", "abc123", "", 10, &group)
	require.NoError(t, err)
	android, err := fixture.updates.CreateUpdateWithRollout(ctx, fixture.appId, 700, rolloutTestDefaultBranch, rolloutTestRuntime, "android", "abc123", "", 10, &group)
	require.NoError(t, err)

	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *ios))
	require.NoError(t, fixture.updates.MarkUpdateAsChecked(ctx, *android),
		"the second platform of a grouped rollout publish must not be blocked by the first one's active rollout")

	active, err := fixture.updates.HasActiveRolloutUpdate(ctx, fixture.appId, rolloutTestDefaultBranch, rolloutTestRuntime)
	require.NoError(t, err)
	assert.True(t, active)

	require.NoError(t, fixture.updates.StoreUpdateUUIDInMetadata(ctx, *ios, uuid.NewString()))
	require.NoError(t, fixture.updates.StoreUpdateUUIDInMetadata(ctx, *android, uuid.NewString()))
	page, err := fixture.updates.GetUpdatesByRunTimeVersionAndBranchName(ctx, fixture.appId, rolloutTestRuntime, rolloutTestDefaultBranch, nil, 100)
	require.NoError(t, err)
	items := page.Items
	require.Len(t, items, 2)
	for _, item := range items {
		require.NotNil(t, item.PublishGroup)
		assert.Equal(t, group, *item.PublishGroup)
		require.NotNil(t, item.RolloutPercentage)
		assert.Equal(t, 10, *item.RolloutPercentage)
	}

	feed, err := fixture.updates.GetUpdateFeed(ctx, fixture.appId, types.UpdateFeedQuery{
		PublishGroup: group,
		Limit:        10,
	})
	require.NoError(t, err)
	require.Len(t, feed, 2)
	for _, item := range feed {
		require.NotNil(t, item.RolloutPercentage)
		assert.Equal(t, 10, *item.RolloutPercentage)
	}
}