Exclude translations from cookie banner version snapshots
Translation changes are cosmetic, not consent-contract changes, so they should not trigger a version bump. Translations are now loaded live from the database at serve time instead of being frozen in the snapshot. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -472,7 +472,7 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCookieBannerVersioning_NoOpTranslation(t *testing.T) {
|
||||
func TestCookieBannerVersioning_TranslationChangesNeverBump(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("re-upserting identical JSON does not bump version", func(t *testing.T) {
|
||||
@@ -481,13 +481,11 @@ func TestCookieBannerVersioning_NoOpTranslation(t *testing.T) {
|
||||
|
||||
bannerID := factory.CreateCookieBanner(owner)
|
||||
|
||||
// Insert a custom translation, then publish.
|
||||
const customJSON = `{"banner_title":"Cookie Bar","button_accept_all":"Accept"}`
|
||||
upsertTranslation(t, owner, bannerID, "it", customJSON)
|
||||
published := publishBanner(t, owner, bannerID)
|
||||
baseline := published.Version
|
||||
|
||||
// Re-upsert the same JSON.
|
||||
upsertTranslation(t, owner, bannerID, "it", customJSON)
|
||||
|
||||
got := latestVersion(t, owner, bannerID)
|
||||
@@ -495,25 +493,35 @@ func TestCookieBannerVersioning_NoOpTranslation(t *testing.T) {
|
||||
assert.Equal(t, "PUBLISHED", got.State)
|
||||
})
|
||||
|
||||
t.Run("whitespace-only and key-order differences do not bump version", func(t *testing.T) {
|
||||
t.Run("changing translation content does not bump version", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
bannerID := factory.CreateCookieBanner(owner)
|
||||
|
||||
const compact = `{"banner_title":"Cookie Bar","button_accept_all":"Accept"}`
|
||||
const reformatted = `{
|
||||
"button_accept_all": "Accept",
|
||||
"banner_title": "Cookie Bar"
|
||||
}`
|
||||
upsertTranslation(t, owner, bannerID, "it", compact)
|
||||
upsertTranslation(t, owner, bannerID, "it", `{"banner_title":"Cookie Bar"}`)
|
||||
published := publishBanner(t, owner, bannerID)
|
||||
baseline := published.Version
|
||||
|
||||
upsertTranslation(t, owner, bannerID, "it", reformatted)
|
||||
upsertTranslation(t, owner, bannerID, "it", `{"banner_title":"Barra dei Cookie"}`)
|
||||
|
||||
got := latestVersion(t, owner, bannerID)
|
||||
assert.Equal(t, baseline, got.Version, "JSON formatting differences should be canonicalised")
|
||||
assert.Equal(t, baseline, got.Version, "translation content changes should not bump the version")
|
||||
assert.Equal(t, "PUBLISHED", got.State)
|
||||
})
|
||||
|
||||
t.Run("adding a new language does not bump version", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
owner := testutil.NewClient(t, testutil.RoleOwner)
|
||||
|
||||
bannerID := factory.CreateCookieBanner(owner)
|
||||
published := publishBanner(t, owner, bannerID)
|
||||
baseline := published.Version
|
||||
|
||||
upsertTranslation(t, owner, bannerID, "fr", `{"banner_title":"Bandeau cookies"}`)
|
||||
|
||||
got := latestVersion(t, owner, bannerID)
|
||||
assert.Equal(t, baseline, got.Version, "adding a new language should not bump the version")
|
||||
assert.Equal(t, "PUBLISHED", got.State)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -358,9 +358,8 @@ func (s *Service) ensureDraftVersion(
|
||||
banner *coredata.CookieBanner,
|
||||
categories coredata.CookieCategories,
|
||||
allPatterns coredata.CookiePatterns,
|
||||
translations coredata.CookieBannerTranslations,
|
||||
) (*coredata.CookieBannerVersion, error) {
|
||||
snapshot := buildSnapshot(banner, categories, allPatterns, translations)
|
||||
snapshot := buildSnapshot(banner, categories, allPatterns)
|
||||
|
||||
var latest coredata.CookieBannerVersion
|
||||
err := latest.LoadLatestByCookieBannerID(ctx, tx, scope, banner.ID)
|
||||
@@ -440,12 +439,7 @@ func (s *Service) ensureDraftVersionForBanner(
|
||||
return nil, fmt.Errorf("cannot load cookie patterns: %w", err)
|
||||
}
|
||||
|
||||
var translations coredata.CookieBannerTranslations
|
||||
if err := translations.LoadAllByCookieBannerID(ctx, tx, scope, bannerID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load cookie banner translations: %w", err)
|
||||
}
|
||||
|
||||
return s.ensureDraftVersion(ctx, tx, scope, &banner, categories, allPatterns, translations)
|
||||
return s.ensureDraftVersion(ctx, tx, scope, &banner, categories, allPatterns)
|
||||
}
|
||||
|
||||
func (s *Service) CreateCookieBanner(
|
||||
@@ -1888,7 +1882,18 @@ func (s *Service) GetActiveBannerConfig(
|
||||
return fmt.Errorf("cannot get version snapshot: %w", err)
|
||||
}
|
||||
|
||||
config = buildBannerConfig(&banner, &version, &snapshot, lang)
|
||||
var categories coredata.CookieCategories
|
||||
if err := categories.LoadAllByCookieBannerID(ctx, conn, scope, banner.ID); err != nil {
|
||||
return fmt.Errorf("cannot load cookie categories: %w", err)
|
||||
}
|
||||
|
||||
var translations coredata.CookieBannerTranslations
|
||||
if err := translations.LoadAllByCookieBannerID(ctx, conn, scope, banner.ID); err != nil {
|
||||
return fmt.Errorf("cannot load cookie banner translations: %w", err)
|
||||
}
|
||||
|
||||
resolved := resolveTranslations(translations, categories)
|
||||
config = buildBannerConfig(&banner, &version, &snapshot, resolved, lang)
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -1904,6 +1909,7 @@ func buildBannerConfig(
|
||||
banner *coredata.CookieBanner,
|
||||
version *coredata.CookieBannerVersion,
|
||||
snapshot *coredata.CookieBannerVersionSnapshot,
|
||||
translations map[string]coredata.CookieBannerVersionSnapshotTranslation,
|
||||
lang string,
|
||||
) *BannerConfig {
|
||||
defaultLang := snapshot.DefaultLanguage
|
||||
@@ -1913,7 +1919,7 @@ func buildBannerConfig(
|
||||
|
||||
resolvedLang := defaultLang
|
||||
if lang != "" {
|
||||
if _, ok := snapshot.Translations[lang]; ok {
|
||||
if _, ok := translations[lang]; ok {
|
||||
resolvedLang = lang
|
||||
}
|
||||
}
|
||||
@@ -1921,7 +1927,7 @@ func buildBannerConfig(
|
||||
categories := snapshot.Categories
|
||||
texts := make(map[string]string)
|
||||
|
||||
if t, ok := snapshot.Translations[resolvedLang]; ok {
|
||||
if t, ok := translations[resolvedLang]; ok {
|
||||
maps.Copy(texts, t.UI)
|
||||
|
||||
if len(t.Categories) == len(categories) {
|
||||
@@ -2038,10 +2044,6 @@ func (s *Service) UpsertCookieBannerTranslation(
|
||||
return fmt.Errorf("cannot load cookie banner translation: %w", err)
|
||||
}
|
||||
|
||||
if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, req.CookieBannerID); err != nil {
|
||||
return fmt.Errorf("cannot ensure draft version: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
@@ -49,14 +49,6 @@ func TestSnapshotsEqual(t *testing.T) {
|
||||
PostHogConsent: false,
|
||||
},
|
||||
},
|
||||
Translations: map[string]coredata.CookieBannerVersionSnapshotTranslation{
|
||||
"fr": {
|
||||
UI: map[string]string{"title": "Cookies"},
|
||||
Categories: []coredata.CookieBannerVersionSnapshotCategoryTranslation{
|
||||
{Name: "Analyse", Description: "Cookies d'analyse"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,19 +95,6 @@ func TestSnapshotsEqual(t *testing.T) {
|
||||
assert.False(t, snapshotsEqual(a, b))
|
||||
})
|
||||
|
||||
t.Run("differing translation UI is not equal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := baseSnapshot()
|
||||
b := baseSnapshot()
|
||||
b.Translations["fr"] = coredata.CookieBannerVersionSnapshotTranslation{
|
||||
UI: map[string]string{"title": "Cookies updated"},
|
||||
Categories: a.Translations["fr"].Categories,
|
||||
}
|
||||
|
||||
assert.False(t, snapshotsEqual(a, b))
|
||||
})
|
||||
|
||||
t.Run("differing GCMConsentTypes order is not equal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -214,8 +193,8 @@ func TestBuildSnapshot_RankInvariant(t *testing.T) {
|
||||
t.Run("snapshot is identical regardless of rank values", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
original := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil, nil)
|
||||
shuffled := buildSnapshot(banner, mkCategories(99, 50, 25, 10), nil, nil)
|
||||
original := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil)
|
||||
shuffled := buildSnapshot(banner, mkCategories(99, 50, 25, 10), nil)
|
||||
|
||||
assert.True(t, snapshotsEqual(original, shuffled), "rank changes must not affect the snapshot")
|
||||
})
|
||||
@@ -226,8 +205,8 @@ func TestBuildSnapshot_RankInvariant(t *testing.T) {
|
||||
ordered := mkCategories(0, 1, 2, 3)
|
||||
reversed := coredata.CookieCategories{ordered[3], ordered[2], ordered[1], ordered[0]}
|
||||
|
||||
a := buildSnapshot(banner, ordered, nil, nil)
|
||||
b := buildSnapshot(banner, reversed, nil, nil)
|
||||
a := buildSnapshot(banner, ordered, nil)
|
||||
b := buildSnapshot(banner, reversed, nil)
|
||||
|
||||
assert.True(t, snapshotsEqual(a, b))
|
||||
})
|
||||
@@ -235,7 +214,7 @@ func TestBuildSnapshot_RankInvariant(t *testing.T) {
|
||||
t.Run("Necessary comes first and Uncategorised comes last", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
snap := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil, nil)
|
||||
snap := buildSnapshot(banner, mkCategories(0, 1, 2, 3), nil)
|
||||
|
||||
require.Len(t, snap.Categories, 4)
|
||||
assert.Equal(t, coredata.CookieCategoryKindNecessary, snap.Categories[0].Kind)
|
||||
|
||||
@@ -24,6 +24,16 @@ import (
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
// resolveTranslations converts raw DB translations into the resolved map
|
||||
// used by buildBannerConfig at serve time. Categories must be sorted in
|
||||
// snapshot order so the positional category translations align.
|
||||
func resolveTranslations(
|
||||
translations coredata.CookieBannerTranslations,
|
||||
categories coredata.CookieCategories,
|
||||
) map[string]coredata.CookieBannerVersionSnapshotTranslation {
|
||||
return buildSnapshotTranslations(translations, sortCategoriesForSnapshot(categories))
|
||||
}
|
||||
|
||||
// snapshotsEqual reports whether two version snapshots are visitor-identical.
|
||||
// buildSnapshot already normalises empty slices and nil maps, so reflect.DeepEqual
|
||||
// is sufficient and is the single chokepoint we'd extend if we ever wanted to
|
||||
@@ -67,7 +77,6 @@ func buildSnapshot(
|
||||
banner *coredata.CookieBanner,
|
||||
categories coredata.CookieCategories,
|
||||
allPatterns coredata.CookiePatterns,
|
||||
translations coredata.CookieBannerTranslations,
|
||||
) coredata.CookieBannerVersionSnapshot {
|
||||
categories = sortCategoriesForSnapshot(categories)
|
||||
|
||||
@@ -104,8 +113,6 @@ func buildSnapshot(
|
||||
}
|
||||
}
|
||||
|
||||
snapshotTranslations := buildSnapshotTranslations(translations, categories)
|
||||
|
||||
return coredata.CookieBannerVersionSnapshot{
|
||||
PrivacyPolicyURL: banner.PrivacyPolicyURL,
|
||||
CookiePolicyURL: banner.CookiePolicyURL,
|
||||
@@ -113,7 +120,6 @@ func buildSnapshot(
|
||||
ConsentMode: string(banner.ConsentMode),
|
||||
DefaultLanguage: banner.DefaultLanguage,
|
||||
Categories: snapshotCategories,
|
||||
Translations: snapshotTranslations,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,12 @@ import (
|
||||
|
||||
type (
|
||||
CookieBannerVersionSnapshot struct {
|
||||
PrivacyPolicyURL *string `json:"privacy_policy_url,omitempty"`
|
||||
CookiePolicyURL string `json:"cookie_policy_url"`
|
||||
ConsentExpiryDays int `json:"consent_expiry_days"`
|
||||
ConsentMode string `json:"consent_mode"`
|
||||
DefaultLanguage string `json:"default_language"`
|
||||
Categories []CookieBannerVersionSnapshotCategory `json:"categories"`
|
||||
Translations map[string]CookieBannerVersionSnapshotTranslation `json:"translations,omitempty"`
|
||||
PrivacyPolicyURL *string `json:"privacy_policy_url,omitempty"`
|
||||
CookiePolicyURL string `json:"cookie_policy_url"`
|
||||
ConsentExpiryDays int `json:"consent_expiry_days"`
|
||||
ConsentMode string `json:"consent_mode"`
|
||||
DefaultLanguage string `json:"default_language"`
|
||||
Categories []CookieBannerVersionSnapshotCategory `json:"categories"`
|
||||
}
|
||||
|
||||
CookieBannerVersionSnapshotTranslation struct {
|
||||
|
||||
Reference in New Issue
Block a user