mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 23:26:38 +08:00
merge: 合并 PR #22 Pages 部署源 V2 到 feat/pages-source-sync-v2
基于最新 main 合并 deqiying/feat/pages-source-sync-v2, 解决 docs/changelog/index.md 与限流相关条目的冲突。
This commit is contained in:
@@ -18,22 +18,23 @@ const (
|
||||
|
||||
// PagesProject OpenFlare Pages 静态托管项目。
|
||||
type PagesProject struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Name string `json:"name" gorm:"size:255;not null"`
|
||||
Slug string `json:"slug" gorm:"uniqueIndex;size:128;not null"`
|
||||
Description string `json:"description" gorm:"type:text;not null;default:''"`
|
||||
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
||||
SPAFallbackEnabled bool `json:"spa_fallback_enabled" gorm:"not null;default:false"`
|
||||
SPAFallbackPath string `json:"spa_fallback_path" gorm:"size:512;not null;default:'/index.html'"`
|
||||
APIProxyEnabled bool `json:"api_proxy_enabled" gorm:"not null;default:false"`
|
||||
APIProxyPath string `json:"api_proxy_path" gorm:"size:255;not null;default:''"`
|
||||
APIProxyPass string `json:"api_proxy_pass" gorm:"size:2048;not null;default:''"`
|
||||
APIProxyRewrite string `json:"api_proxy_rewrite" gorm:"size:255;not null;default:''"`
|
||||
ActiveDeploymentID *uint `json:"active_deployment_id" gorm:"index"`
|
||||
RootDir string `json:"root_dir" gorm:"size:512;not null;default:''"`
|
||||
EntryFile string `json:"entry_file" gorm:"size:512;not null;default:'index.html'"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Name string `json:"name" gorm:"size:255;not null"`
|
||||
Slug string `json:"slug" gorm:"uniqueIndex;size:128;not null"`
|
||||
Description string `json:"description" gorm:"type:text;not null;default:''"`
|
||||
Enabled bool `json:"enabled" gorm:"not null;default:true"`
|
||||
SPAFallbackEnabled bool `json:"spa_fallback_enabled" gorm:"not null;default:false"`
|
||||
SPAFallbackPath string `json:"spa_fallback_path" gorm:"size:512;not null;default:'/index.html'"`
|
||||
APIProxyEnabled bool `json:"api_proxy_enabled" gorm:"not null;default:false"`
|
||||
APIProxyPath string `json:"api_proxy_path" gorm:"size:255;not null;default:''"`
|
||||
APIProxyPass string `json:"api_proxy_pass" gorm:"size:2048;not null;default:''"`
|
||||
APIProxyRewrite string `json:"api_proxy_rewrite" gorm:"size:255;not null;default:''"`
|
||||
ActiveDeploymentID *uint `json:"active_deployment_id" gorm:"index"`
|
||||
RootDir string `json:"root_dir" gorm:"size:512;not null;default:''"`
|
||||
EntryFile string `json:"entry_file" gorm:"size:512;not null;default:'index.html'"`
|
||||
ContentConfigVersion int `json:"-" gorm:"not null;default:0"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// TableName 表名。
|
||||
@@ -44,8 +45,8 @@ func (PagesProject) TableName() string {
|
||||
// PagesDeployment OpenFlare Pages 不可变部署记录。
|
||||
type PagesDeployment struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
ProjectID uint `json:"project_id" gorm:"not null;index"`
|
||||
DeploymentNumber int `json:"deployment_number" gorm:"not null"`
|
||||
ProjectID uint `json:"project_id" gorm:"not null;index;uniqueIndex:idx_of_pages_deployments_project_number,priority:1;uniqueIndex:idx_of_pages_deployments_source_revision,priority:1,where:source_identity IS NOT NULL AND source_revision IS NOT NULL"`
|
||||
DeploymentNumber int `json:"deployment_number" gorm:"not null;uniqueIndex:idx_of_pages_deployments_project_number,priority:2"`
|
||||
Checksum string `json:"checksum" gorm:"size:64;not null;index"`
|
||||
Status string `json:"status" gorm:"size:32;not null;default:'uploaded';index"`
|
||||
UploadID uint64 `json:"upload_id,string" gorm:"not null;default:0;index"`
|
||||
@@ -53,6 +54,12 @@ type PagesDeployment struct {
|
||||
FileCount int `json:"file_count" gorm:"not null;default:0"`
|
||||
TotalSize int64 `json:"total_size" gorm:"not null;default:0"`
|
||||
CreatedBy string `json:"created_by" gorm:"size:64;not null;default:''"`
|
||||
SourceType string `json:"source_type" gorm:"size:32;not null;default:''"`
|
||||
SourceIdentity *string `json:"-" gorm:"type:char(64);uniqueIndex:idx_of_pages_deployments_source_revision,priority:2,where:source_identity IS NOT NULL AND source_revision IS NOT NULL"`
|
||||
SourceRevision *string `json:"-" gorm:"type:char(64);uniqueIndex:idx_of_pages_deployments_source_revision,priority:3,where:source_identity IS NOT NULL AND source_revision IS NOT NULL"`
|
||||
SourceLabel string `json:"source_label" gorm:"size:255;not null;default:''"`
|
||||
SourceMeta string `json:"-" gorm:"type:text;not null;default:''"`
|
||||
TriggerType string `json:"trigger_type" gorm:"size:32;not null;default:''"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
ActivatedAt *time.Time `json:"activated_at"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
)
|
||||
|
||||
const (
|
||||
// PagesOrphanUploadCandidateLimit bounds one delayed Pages upload cleanup pass.
|
||||
PagesOrphanUploadCandidateLimit = 100
|
||||
|
||||
pagesOrphanMarkerPredicatePostgres = "w_uploads.metadata #>> '{extra,pages_ingest_marker}' = ?"
|
||||
pagesOrphanMarkerPredicateSQLite = "CASE WHEN json_valid(w_uploads.metadata) THEN json_extract(w_uploads.metadata, '$.extra.pages_ingest_marker') ELSE NULL END = ?"
|
||||
)
|
||||
|
||||
// PagesOrphanUploadCandidateQuery describes the fail-closed SQL candidate set
|
||||
// for delayed Pages upload compensation.
|
||||
type PagesOrphanUploadCandidateQuery struct {
|
||||
SystemUserID uint64
|
||||
UploadType string
|
||||
Marker string
|
||||
CreatedBefore time.Time
|
||||
}
|
||||
|
||||
// ListPagesOrphanUploadCandidates returns at most 100 unreferenced, isolated
|
||||
// Pages V2 upload records. Callers must still lock and recheck every condition
|
||||
// before deleting a candidate.
|
||||
func ListPagesOrphanUploadCandidates(
|
||||
ctx context.Context,
|
||||
input PagesOrphanUploadCandidateQuery,
|
||||
) ([]Upload, error) {
|
||||
if input.SystemUserID == 0 || input.UploadType == "" || input.Marker == "" || input.CreatedBefore.IsZero() {
|
||||
return nil, errors.New("invalid pages orphan upload candidate query")
|
||||
}
|
||||
markerPredicate, err := pagesOrphanMarkerPredicate(db.DB(ctx).Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deploymentTable := (PagesDeployment{}).TableName()
|
||||
uploadTable := (Upload{}).TableName()
|
||||
var candidates []Upload
|
||||
err = db.DB(ctx).
|
||||
Model(&Upload{}).
|
||||
Where(uploadTable+".status = ?", UploadStatusUsed).
|
||||
Where(uploadTable+".user_id = ?", input.SystemUserID).
|
||||
Where(uploadTable+".type = ?", input.UploadType).
|
||||
Where(uploadTable+".created_at < ?", input.CreatedBefore).
|
||||
Where(markerPredicate, input.Marker).
|
||||
Where("NOT EXISTS (SELECT 1 FROM " + deploymentTable + " WHERE " + deploymentTable + ".upload_id = " + uploadTable + ".id)").
|
||||
Order(uploadTable + ".id ASC").
|
||||
Limit(PagesOrphanUploadCandidateLimit).
|
||||
Find(&candidates).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func pagesOrphanMarkerPredicate(dialect string) (string, error) {
|
||||
switch dialect {
|
||||
case "postgres":
|
||||
return pagesOrphanMarkerPredicatePostgres, nil
|
||||
case "sqlite":
|
||||
return pagesOrphanMarkerPredicateSQLite, nil
|
||||
default:
|
||||
return "", errors.New("unsupported database dialect for Pages orphan cleanup")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/db"
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestPagesOrphanMarkerPredicate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dialect string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "postgres jsonb path",
|
||||
dialect: "postgres",
|
||||
want: "metadata #>> '{extra,pages_ingest_marker}'",
|
||||
},
|
||||
{
|
||||
name: "sqlite guarded json extract",
|
||||
dialect: "sqlite",
|
||||
want: "CASE WHEN json_valid(w_uploads.metadata) THEN json_extract",
|
||||
},
|
||||
{
|
||||
name: "unknown dialect rejected",
|
||||
dialect: "mysql",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := pagesOrphanMarkerPredicate(test.dialect)
|
||||
if gotErr := err != nil; gotErr != test.wantErr {
|
||||
t.Fatalf("pagesOrphanMarkerPredicate(%q) error = %v, want error presence = %t", test.dialect, err, test.wantErr)
|
||||
}
|
||||
if test.want != "" && !strings.Contains(got, test.want) {
|
||||
t.Errorf("pagesOrphanMarkerPredicate(%q) = %q, want substring %q", test.dialect, got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPagesOrphanUploadCandidatesFiltersAndLimits(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
gormDB := setupPagesCleanupModelTestDB(t)
|
||||
cutoff := time.Now().UTC().Add(-2 * time.Hour)
|
||||
old := cutoff.Add(-time.Minute)
|
||||
marker := UploadMetadata{Extra: map[string]any{
|
||||
"pages_ingest_marker": "pages_deployment_v2",
|
||||
"pages_project_id": "1",
|
||||
}}
|
||||
|
||||
valid := make([]Upload, 0, PagesOrphanUploadCandidateLimit+1)
|
||||
for index := 0; index < PagesOrphanUploadCandidateLimit+1; index++ {
|
||||
valid = append(valid, pagesCleanupModelUpload(uint64(index+100), 999, "openflare_pages_deployment", UploadStatusUsed, old, marker))
|
||||
}
|
||||
if err := gormDB.Create(&valid).Error; err != nil {
|
||||
t.Fatalf("create valid candidates error = %v, want nil", err)
|
||||
}
|
||||
|
||||
referenced := pagesCleanupModelUpload(1, 999, "openflare_pages_deployment", UploadStatusUsed, old, marker)
|
||||
wrongOwner := pagesCleanupModelUpload(2, 1000, "openflare_pages_deployment", UploadStatusUsed, old, marker)
|
||||
wrongType := pagesCleanupModelUpload(3, 999, "generic", UploadStatusUsed, old, marker)
|
||||
wrongStatus := pagesCleanupModelUpload(4, 999, "openflare_pages_deployment", UploadStatusPending, old, marker)
|
||||
fresh := pagesCleanupModelUpload(5, 999, "openflare_pages_deployment", UploadStatusUsed, cutoff, marker)
|
||||
wrongMarker := pagesCleanupModelUpload(6, 999, "openflare_pages_deployment", UploadStatusUsed, old, UploadMetadata{Extra: map[string]any{
|
||||
"pages_ingest_marker": "pages_deployment_v1",
|
||||
"pages_project_id": "1",
|
||||
}})
|
||||
for _, upload := range []Upload{referenced, wrongOwner, wrongType, wrongStatus, fresh, wrongMarker} {
|
||||
if err := gormDB.Create(&upload).Error; err != nil {
|
||||
t.Fatalf("create filtered upload %d error = %v, want nil", upload.ID, err)
|
||||
}
|
||||
}
|
||||
if err := gormDB.Create(&PagesDeployment{
|
||||
ProjectID: 1,
|
||||
DeploymentNumber: 1,
|
||||
Checksum: "referenced",
|
||||
Status: PagesDeploymentStatusUploaded,
|
||||
UploadID: referenced.ID,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create referenced deployment error = %v, want nil", err)
|
||||
}
|
||||
|
||||
invalidJSON := pagesCleanupModelUpload(7, 999, "openflare_pages_deployment", UploadStatusUsed, old, marker)
|
||||
if err := gormDB.Create(&invalidJSON).Error; err != nil {
|
||||
t.Fatalf("create invalid JSON upload error = %v, want nil", err)
|
||||
}
|
||||
if err := gormDB.Table((Upload{}).TableName()).Where("id = ?", invalidJSON.ID).
|
||||
UpdateColumn("metadata", "{invalid").Error; err != nil {
|
||||
t.Fatalf("corrupt upload metadata error = %v, want nil", err)
|
||||
}
|
||||
|
||||
got, err := ListPagesOrphanUploadCandidates(ctx, PagesOrphanUploadCandidateQuery{
|
||||
SystemUserID: 999,
|
||||
UploadType: "openflare_pages_deployment",
|
||||
Marker: "pages_deployment_v2",
|
||||
CreatedBefore: cutoff,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ListPagesOrphanUploadCandidates() error = %v, want nil", err)
|
||||
}
|
||||
if len(got) != PagesOrphanUploadCandidateLimit {
|
||||
t.Fatalf("ListPagesOrphanUploadCandidates() count = %d, want %d", len(got), PagesOrphanUploadCandidateLimit)
|
||||
}
|
||||
for index, candidate := range got {
|
||||
wantID := uint64(index + 100)
|
||||
if candidate.ID != wantID {
|
||||
t.Errorf("ListPagesOrphanUploadCandidates()[%d].ID = %d, want %d", index, candidate.ID, wantID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPagesOrphanUploadCandidatesSkipsInvalidSQLiteJSON(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
gormDB := setupPagesCleanupModelTestDB(t)
|
||||
cutoff := time.Now().UTC().Add(-2 * time.Hour)
|
||||
upload := pagesCleanupModelUpload(1, 999, "openflare_pages_deployment", UploadStatusUsed, cutoff.Add(-time.Minute), UploadMetadata{})
|
||||
if err := gormDB.Create(&upload).Error; err != nil {
|
||||
t.Fatalf("create invalid JSON candidate error = %v, want nil", err)
|
||||
}
|
||||
if err := gormDB.Table((Upload{}).TableName()).Where("id = ?", upload.ID).
|
||||
UpdateColumn("metadata", "{invalid").Error; err != nil {
|
||||
t.Fatalf("corrupt upload metadata error = %v, want nil", err)
|
||||
}
|
||||
|
||||
got, err := ListPagesOrphanUploadCandidates(ctx, PagesOrphanUploadCandidateQuery{
|
||||
SystemUserID: 999,
|
||||
UploadType: "openflare_pages_deployment",
|
||||
Marker: "pages_deployment_v2",
|
||||
CreatedBefore: cutoff,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ListPagesOrphanUploadCandidates(invalid JSON) error = %v, want nil", err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("ListPagesOrphanUploadCandidates(invalid JSON) count = %d, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func setupPagesCleanupModelTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
|
||||
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("open Pages cleanup model test database error = %v, want nil", err)
|
||||
}
|
||||
if err := gormDB.AutoMigrate(&Upload{}, &PagesDeployment{}); err != nil {
|
||||
t.Fatalf("migrate Pages cleanup model test database error = %v, want nil", err)
|
||||
}
|
||||
db.SetDB(gormDB)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
return gormDB
|
||||
}
|
||||
|
||||
func pagesCleanupModelUpload(
|
||||
id uint64,
|
||||
userID uint64,
|
||||
uploadType string,
|
||||
status UploadStatus,
|
||||
createdAt time.Time,
|
||||
metadata UploadMetadata,
|
||||
) Upload {
|
||||
return Upload{
|
||||
ID: id,
|
||||
UserID: userID,
|
||||
FileName: "site.zip",
|
||||
FilePath: "pages/site.zip",
|
||||
FileSize: 10,
|
||||
MimeType: "application/zip",
|
||||
Extension: "zip",
|
||||
Hash: "checksum",
|
||||
Type: uploadType,
|
||||
Status: status,
|
||||
AccessMode: 0,
|
||||
Metadata: metadata,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: createdAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// PagesProjectSource 保存 Pages 项目的持久部署源配置。
|
||||
//
|
||||
// RemoteURL 可能包含签名参数,禁止直接序列化 model;对外接口必须映射到
|
||||
// pages 包内的脱敏 source view。
|
||||
type PagesProjectSource struct {
|
||||
ID uint `json:"-" gorm:"primaryKey;autoIncrement"`
|
||||
ProjectID uint `json:"-" gorm:"not null;uniqueIndex:idx_of_pages_project_sources_project_id"`
|
||||
SourceType string `json:"-" gorm:"size:32;not null;default:''"`
|
||||
RemoteURL string `json:"-" gorm:"type:text;not null;default:''"`
|
||||
RemoteNetworkPolicy string `json:"-" gorm:"size:32;not null;default:''"`
|
||||
GitHubRepository string `json:"-" gorm:"column:github_repository;size:255;not null;default:''"`
|
||||
ReleaseSelector string `json:"-" gorm:"size:16;not null;default:''"`
|
||||
ReleaseTag string `json:"-" gorm:"size:255;not null;default:''"`
|
||||
AssetName string `json:"-" gorm:"size:255;not null;default:''"`
|
||||
AutoUpdateEnabled bool `json:"-" gorm:"not null;default:false"`
|
||||
CheckIntervalMinutes int `json:"-" gorm:"not null;default:0"`
|
||||
ConfigVersion int `json:"-" gorm:"not null;default:0"`
|
||||
SourceIdentity string `json:"-" gorm:"type:char(64);not null;default:''"`
|
||||
CreatedAt time.Time `json:"-" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// TableName 返回 Pages 项目部署源配置表名。
|
||||
func (PagesProjectSource) TableName() string {
|
||||
return "of_pages_project_sources"
|
||||
}
|
||||
|
||||
// PagesProjectSourceRuntime 保存 Pages 项目部署源的可变运行态。
|
||||
//
|
||||
// Runtime 不冗余 project_id;调用方通过 SourceID 关联配置,并在最终提交时
|
||||
// 同时校验 source config version 与 project content config version。
|
||||
type PagesProjectSourceRuntime struct {
|
||||
SourceID uint `json:"-" gorm:"primaryKey;autoIncrement:false"`
|
||||
ETag string `json:"-" gorm:"column:etag;size:512;not null;default:''"`
|
||||
LastSeenRevision string `json:"-" gorm:"type:char(64);not null;default:''"`
|
||||
LastSeenDetail string `json:"-" gorm:"type:text;not null;default:''"`
|
||||
LastAppliedRevision string `json:"-" gorm:"type:char(64);not null;default:''"`
|
||||
LastAppliedDetail string `json:"-" gorm:"type:text;not null;default:''"`
|
||||
SyncStatus string `json:"-" gorm:"size:32;not null;default:''"`
|
||||
LastError string `json:"-" gorm:"type:text;not null;default:''"`
|
||||
LastCheckedAt *time.Time `json:"-"`
|
||||
LastSyncedAt *time.Time `json:"-"`
|
||||
NextCheckAt *time.Time `json:"-" gorm:"index:idx_of_pages_project_source_runtime_next_check_at"`
|
||||
LeaseExpiresAt *time.Time `json:"-"`
|
||||
LeaseToken string `json:"-" gorm:"size:64;not null;default:''"`
|
||||
UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// TableName 返回 Pages 项目部署源运行态表名。
|
||||
func (PagesProjectSourceRuntime) TableName() string {
|
||||
return "of_pages_project_source_runtime"
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestPagesSourceModelsMatchMigrationSchema(t *testing.T) {
|
||||
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, gormDB.AutoMigrate(
|
||||
&PagesProject{},
|
||||
&PagesDeployment{},
|
||||
&PagesProjectSource{},
|
||||
&PagesProjectSourceRuntime{},
|
||||
))
|
||||
|
||||
assert.Equal(t, "of_pages_project_sources", (PagesProjectSource{}).TableName())
|
||||
assert.Equal(t, "of_pages_project_source_runtime", (PagesProjectSourceRuntime{}).TableName())
|
||||
assert.True(t, gormDB.Migrator().HasColumn(&PagesProjectSource{}, "github_repository"))
|
||||
assert.False(t, gormDB.Migrator().HasColumn(&PagesProjectSource{}, "git_hub_repository"))
|
||||
assert.True(t, gormDB.Migrator().HasColumn(&PagesProjectSourceRuntime{}, "etag"))
|
||||
assert.False(t, gormDB.Migrator().HasColumn(&PagesProjectSourceRuntime{}, "e_tag"))
|
||||
|
||||
var indexSQL string
|
||||
require.NoError(t, gormDB.Raw(
|
||||
"SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?",
|
||||
"idx_of_pages_deployments_source_revision",
|
||||
).Scan(&indexSQL).Error)
|
||||
assert.Contains(t, strings.ToUpper(indexSQL), "WHERE SOURCE_IDENTITY IS NOT NULL AND SOURCE_REVISION IS NOT NULL")
|
||||
}
|
||||
|
||||
func TestPagesSourceModelsDoNotSerializeSecretsOrFencingState(t *testing.T) {
|
||||
sourceJSON, err := json.Marshal(PagesProjectSource{
|
||||
ID: 1,
|
||||
ProjectID: 2,
|
||||
RemoteURL: "https://example.com/site.zip?token=secret",
|
||||
ConfigVersion: 3,
|
||||
SourceIdentity: strings.Repeat("a", 64),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{}`, string(sourceJSON))
|
||||
|
||||
runtimeJSON, err := json.Marshal(PagesProjectSourceRuntime{
|
||||
SourceID: 1,
|
||||
ETag: `"secret-etag"`,
|
||||
LeaseToken: "secret-lease",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, `{}`, string(runtimeJSON))
|
||||
|
||||
identity := strings.Repeat("b", 64)
|
||||
revision := strings.Repeat("c", 64)
|
||||
deploymentJSON, err := json.Marshal(PagesDeployment{
|
||||
SourceType: "remote_url",
|
||||
SourceIdentity: &identity,
|
||||
SourceRevision: &revision,
|
||||
SourceLabel: "site.zip",
|
||||
SourceMeta: `{"provider":"remote_url","private":"secret"}`,
|
||||
TriggerType: "manual_sync",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, string(deploymentJSON), identity)
|
||||
assert.NotContains(t, string(deploymentJSON), revision)
|
||||
assert.NotContains(t, string(deploymentJSON), "private")
|
||||
assert.Contains(t, string(deploymentJSON), `"source_type":"remote_url"`)
|
||||
assert.Contains(t, string(deploymentJSON), `"source_label":"site.zip"`)
|
||||
assert.Contains(t, string(deploymentJSON), `"trigger_type":"manual_sync"`)
|
||||
}
|
||||
Reference in New Issue
Block a user