This commit is contained in:
@@ -10,7 +10,6 @@ game:
|
||||
id: palworld
|
||||
name: Palworld
|
||||
description: DoGaMa template for a Palworld dedicated server, including the private REST API used by the optional integration.
|
||||
image: https://www.codesproduit.fr/wp-content/uploads/2024/03/PALWORLD-PC-COVER.jpg
|
||||
website: https://www.palworldgame.com/
|
||||
artwork:
|
||||
logo: assets/icon.png
|
||||
@@ -42,7 +41,7 @@ container:
|
||||
assets:
|
||||
- source: assets/helper.sh
|
||||
destination: /pal/helper.sh
|
||||
sha256: 52e58fe4e22654d0d312fe2bb6195db61dad5ffce78e0440a951d33d20f2c36f
|
||||
sha256: b9f30aba43886644399c7a6d40c03fec6a9f453ba6f12a881eccc1bc3de7a1d3
|
||||
read_only: true
|
||||
stop_timeout_seconds: 120
|
||||
ports:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 786 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 782 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
@@ -0,0 +1,431 @@
|
||||
schema_version: 1
|
||||
id: vrising-didstopia
|
||||
version: 1.0.0
|
||||
|
||||
source:
|
||||
type: verified_community
|
||||
url: https://github.com/Didstopia/vrising-server
|
||||
|
||||
game:
|
||||
id: vrising
|
||||
name: V Rising
|
||||
description: DoGaMa template for a V Rising dedicated server using the Didstopia Docker image.
|
||||
website: https://playvrising.com/
|
||||
artwork:
|
||||
logo: assets/icon.png
|
||||
image: assets/banner.jpg
|
||||
poster: assets/poster.jpg
|
||||
attribution: V Rising and related artwork are property of Stunlock Studios; no affiliation is implied.
|
||||
|
||||
requirements:
|
||||
minimum:
|
||||
cpu_cores: 2
|
||||
memory_mb: 4096
|
||||
storage_gb: 15
|
||||
recommended:
|
||||
cpu_cores: 4
|
||||
memory_mb: 8192
|
||||
storage_gb: 25
|
||||
|
||||
container:
|
||||
image: didstopia/vrising-server
|
||||
tag: latest
|
||||
user_mode: image
|
||||
stop_timeout_seconds: 120
|
||||
|
||||
ports:
|
||||
- id: game
|
||||
container_port: 9876
|
||||
protocol: udp
|
||||
purpose: game
|
||||
publish: true
|
||||
required: true
|
||||
|
||||
- id: query
|
||||
container_port: 9877
|
||||
protocol: udp
|
||||
purpose: query
|
||||
publish: true
|
||||
required: true
|
||||
|
||||
- id: rcon
|
||||
container_port: 9878
|
||||
protocol: tcp
|
||||
purpose: integration
|
||||
publish: false
|
||||
required: true
|
||||
|
||||
storage:
|
||||
mounts:
|
||||
- id: persistent
|
||||
container_path: /app/vrising
|
||||
category: player_data
|
||||
backup: true
|
||||
user_configurable: true
|
||||
read_only: false
|
||||
|
||||
- id: server
|
||||
container_path: /steamcmd/vrising
|
||||
category: runtime
|
||||
backup: false
|
||||
user_configurable: false
|
||||
read_only: false
|
||||
|
||||
configuration:
|
||||
fields:
|
||||
- id: persistent_data_path
|
||||
label: Persistent data path
|
||||
type: string
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_PERSISTENT_DATA_PATH
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: /app/vrising
|
||||
|
||||
- id: branch
|
||||
label: Steam branch
|
||||
type: string
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_BRANCH
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: public
|
||||
|
||||
- id: start_mode
|
||||
label: Server start mode
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_START_MODE
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 0
|
||||
minimum: 0
|
||||
maximum: 2
|
||||
|
||||
- id: update_mode
|
||||
label: Automatic server updates
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_UPDATE_MODE
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: false
|
||||
|
||||
- id: default_host_settings
|
||||
label: Manage host settings from DoGaMa
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_DEFAULT_HOST_SETTINGS
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: true
|
||||
|
||||
- id: default_game_settings
|
||||
label: Manage game settings from DoGaMa
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_DEFAULT_GAME_SETTINGS
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: true
|
||||
|
||||
- id: server_name
|
||||
label: Server name
|
||||
type: string
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_NAME
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: DoGaMa V Rising Server
|
||||
|
||||
- id: server_description
|
||||
label: Server description
|
||||
type: string
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_DESCRIPTION
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: false
|
||||
default: Managed by DoGaMa
|
||||
|
||||
- id: server_password
|
||||
label: Player password
|
||||
type: secret
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_PASSWORD
|
||||
apply: restart_required
|
||||
visibility: secret
|
||||
required: false
|
||||
|
||||
- id: game_port
|
||||
label: Game port
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_GAME_PORT
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 9876
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
|
||||
- id: query_port
|
||||
label: Query port
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_QUERY_PORT
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 9877
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
|
||||
- id: rcon_enabled
|
||||
label: Enable RCON
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_RCON_ENABLED
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: true
|
||||
|
||||
- id: rcon_port
|
||||
label: RCON port
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_RCON_PORT
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 9878
|
||||
minimum: 1
|
||||
maximum: 65535
|
||||
|
||||
- id: rcon_password
|
||||
label: RCON password
|
||||
type: secret
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_RCON_PASSWORD
|
||||
apply: restart_required
|
||||
visibility: secret
|
||||
required: true
|
||||
|
||||
- id: max_players
|
||||
label: Maximum players
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_MAX_CONNECTED_USERS
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: 40
|
||||
minimum: 1
|
||||
maximum: 60
|
||||
|
||||
- id: max_admins
|
||||
label: Maximum administrators
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_MAX_CONNECTED_ADMINS
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 4
|
||||
minimum: 1
|
||||
maximum: 20
|
||||
|
||||
- id: save_name
|
||||
label: Save name
|
||||
type: string
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_SAVE_NAME
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: dogama
|
||||
|
||||
- id: list_on_steam
|
||||
label: List server on Steam
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_LIST_ON_STEAM
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: true
|
||||
|
||||
- id: list_on_epic_eos
|
||||
label: List server on Epic Online Services
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_LIST_ON_EPIC_EOS
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: false
|
||||
|
||||
- id: auto_save_count
|
||||
label: Autosave count
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_AUTO_SAVE_COUNT
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 50
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
|
||||
- id: auto_save_interval
|
||||
label: Autosave interval
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_AUTO_SAVE_INTERVAL
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 300
|
||||
minimum: 60
|
||||
maximum: 3600
|
||||
|
||||
- id: game_settings_preset
|
||||
label: Game settings preset
|
||||
type: string
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_GAME_SETTINGS_PRESET
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
- id: clan_size
|
||||
label: Clan size
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_CLAN_SIZE
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: 4
|
||||
minimum: 1
|
||||
maximum: 10
|
||||
|
||||
- id: pvp
|
||||
label: Enable PvP
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_GAME_ENABLE_PVP
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: false
|
||||
|
||||
- id: disable_blood_drain
|
||||
label: Disable castle blood drain
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_GAME_DISABLE_BLOOD_DRAIN
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: false
|
||||
|
||||
- id: disable_decay
|
||||
label: Disable castle decay
|
||||
type: boolean
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_GAME_DISABLE_DECAY
|
||||
apply: restart_required
|
||||
visibility: members
|
||||
required: true
|
||||
default: false
|
||||
|
||||
- id: job_worker_count
|
||||
label: Unity job worker count
|
||||
type: integer
|
||||
target:
|
||||
kind: environment
|
||||
name: V_RISING_SERVER_JOB_WORKER_COUNT
|
||||
apply: restart_required
|
||||
visibility: admin
|
||||
required: true
|
||||
default: 8
|
||||
minimum: 1
|
||||
maximum: 32
|
||||
|
||||
capabilities:
|
||||
- graceful_shutdown
|
||||
|
||||
backup:
|
||||
strategy: stop_then_archive
|
||||
source_mounts:
|
||||
- persistent
|
||||
restart_after_backup: true
|
||||
|
||||
healthcheck:
|
||||
type: tcp
|
||||
port_id: rcon
|
||||
startup_timeout_seconds: 600
|
||||
interval_seconds: 10
|
||||
|
||||
mods:
|
||||
supported: false
|
||||
|
||||
imports:
|
||||
supported: true
|
||||
accepted_formats:
|
||||
- zip
|
||||
- tar
|
||||
- tar.gz
|
||||
- tar.zst
|
||||
- directory
|
||||
max_extracted_size_gb: 50
|
||||
required_paths: []
|
||||
destination_mount: persistent
|
||||
destination_relative_path: Saves
|
||||
requires_stopped_server: true
|
||||
|
||||
updates:
|
||||
backup_before_update: true
|
||||
automatic_default: false
|
||||
rollback_on_failure: true
|
||||
health_timeout_seconds: 600
|
||||
|
||||
compatibility:
|
||||
minimum_manager_version: 1.0.0
|
||||
requires_instance_migration: false
|
||||
@@ -12,23 +12,49 @@ import (
|
||||
|
||||
func TestInitializeOfficialAndScanDirPreservesLocalFiles(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
if err := catalog.InitializeOfficial(root, catalogdata.Files); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
first, err := catalog.ScanDir(root)
|
||||
if err != nil || len(first.Valid) != 1 || first.Valid[0].Template.Game.Image == "" {
|
||||
t.Fatalf("first scan = %#v, %v", first, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(first.Valid[0].Template.Configuration.Fields) == 0 {
|
||||
t.Fatal("configuration fields were lost")
|
||||
|
||||
if len(first.Valid) < 2 {
|
||||
t.Fatalf("expected at least 2 valid official templates, got %#v", first)
|
||||
}
|
||||
|
||||
var palworld *catalog.Snapshot
|
||||
for i := range first.Valid {
|
||||
if first.Valid[i].Template.ID == "palworld-official" {
|
||||
palworld = &first.Valid[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if palworld == nil {
|
||||
t.Fatal(`official template "palworld-official" is missing`)
|
||||
}
|
||||
|
||||
if palworld.Template.Game.Artwork.Image == "" {
|
||||
t.Fatal("palworld artwork image was lost")
|
||||
}
|
||||
|
||||
if len(palworld.Template.Configuration.Fields) == 0 {
|
||||
t.Fatal("palworld configuration fields were lost")
|
||||
}
|
||||
|
||||
path := filepath.Join(root, "palworld", "template.yaml")
|
||||
if err := os.WriteFile(path, []byte("local customization"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := catalog.InitializeOfficial(root, catalogdata.Files); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
body, err := os.ReadFile(path)
|
||||
if err != nil || string(body) != "local customization" {
|
||||
t.Fatalf("local template was overwritten: %q, %v", body, err)
|
||||
@@ -37,42 +63,54 @@ func TestInitializeOfficialAndScanDirPreservesLocalFiles(t *testing.T) {
|
||||
|
||||
func TestScanDirKeepsValidTemplatesWhenOneIsInvalid(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
|
||||
if err := catalog.InitializeOfficial(root, catalogdata.Files); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
body, err := catalogdata.Files.ReadFile("palworld/template.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
broken := strings.Replace(string(body), "image: https://www.codesproduit.fr/wp-content/uploads/2024/03/PALWORLD-PC-COVER.jpg", "image: not-a-url", 1)
|
||||
|
||||
broken := strings.Replace(
|
||||
string(body),
|
||||
"image: assets/banner.jpg",
|
||||
"image: ../invalid.jpg",
|
||||
1,
|
||||
)
|
||||
|
||||
if err := os.Mkdir(filepath.Join(root, "broken"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "broken", "template.yaml"), []byte(broken), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, err := catalog.ScanDir(root)
|
||||
if err != nil || result.Found != 2 || len(result.Valid) != 1 || len(result.Errors) != 1 {
|
||||
t.Fatalf("scan = %#v, %v", result, err)
|
||||
}
|
||||
if result.Errors[0].Template != "broken" || !strings.Contains(result.Errors[0].Message, "/game/image") {
|
||||
t.Fatalf("error = %#v", result.Errors)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanDirRejectsTemplateSymlinks(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := catalog.InitializeOfficial(root, catalogdata.Files); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(filepath.Join(root, "palworld", "assets", "icon.png"), filepath.Join(root, "palworld", "assets", "linked-icon.png")); err != nil {
|
||||
if err := os.WriteFile(
|
||||
filepath.Join(root, "broken", "template.yaml"),
|
||||
[]byte(broken),
|
||||
0o644,
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result, err := catalog.ScanDir(root)
|
||||
if err != nil || len(result.Valid) != 0 || len(result.Errors) != 1 {
|
||||
t.Fatalf("scan = %#v, %v", result, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Errors[0].Message != "template contains unsupported symbolic links" {
|
||||
|
||||
if result.Found != 3 {
|
||||
t.Fatalf("found = %d, want 3; scan = %#v", result.Found, result)
|
||||
}
|
||||
|
||||
if len(result.Valid) != 2 {
|
||||
t.Fatalf("valid = %d, want 2; scan = %#v", len(result.Valid), result)
|
||||
}
|
||||
|
||||
if len(result.Errors) != 1 {
|
||||
t.Fatalf("errors = %d, want 1; scan = %#v", len(result.Errors), result)
|
||||
}
|
||||
|
||||
if result.Errors[0].Template != "broken" ||
|
||||
!strings.Contains(result.Errors[0].Message, "/game/artwork/image") {
|
||||
t.Fatalf("error = %#v", result.Errors)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +49,11 @@ type Template struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Image string `json:"image"`
|
||||
Artwork struct {
|
||||
Logo string `json:"logo"`
|
||||
Image string `json:"image"`
|
||||
Poster string `json:"poster,omitempty"`
|
||||
LogoSourceURL string `json:"logo_source_url,omitempty"`
|
||||
Attribution string `json:"attribution"`
|
||||
Logo string `json:"logo"`
|
||||
Image string `json:"image"`
|
||||
Poster string `json:"poster"`
|
||||
Attribution string `json:"attribution"`
|
||||
} `json:"artwork"`
|
||||
} `json:"game"`
|
||||
Requirements struct {
|
||||
@@ -402,9 +400,20 @@ func validationErrors(err error, document *yaml.Node) error {
|
||||
|
||||
func crossValidate(template Template, assetRoot string, source fs.FS) []ValidationIssue {
|
||||
var issues []ValidationIssue
|
||||
for field, asset := range map[string]string{"logo": template.Game.Artwork.Logo, "image": template.Game.Artwork.Image} {
|
||||
for field, asset := range map[string]string{
|
||||
"logo": template.Game.Artwork.Logo,
|
||||
"image": template.Game.Artwork.Image,
|
||||
"poster": template.Game.Artwork.Poster,
|
||||
} {
|
||||
if asset == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := fs.Stat(source, path.Join(assetRoot, asset)); err != nil {
|
||||
issues = append(issues, ValidationIssue{Path: "/game/artwork/" + field, Message: "artwork asset is missing"})
|
||||
issues = append(issues, ValidationIssue{
|
||||
Path: "/game/artwork/" + field,
|
||||
Message: "artwork asset is missing",
|
||||
})
|
||||
}
|
||||
}
|
||||
ports := make(map[string]Port)
|
||||
|
||||
@@ -18,11 +18,67 @@ func TestBuiltInCatalogValidatesDeterministically(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(first) != 1 || first[0].Template.ID != "palworld-official" || first[0].Digest != second[0].Digest || first[0].CanonicalYAML != second[0].CanonicalYAML {
|
||||
t.Fatalf("catalog snapshots = %#v, %#v", first, second)
|
||||
|
||||
if len(first) != len(second) {
|
||||
t.Fatalf("catalog snapshot count differs: first=%d second=%d", len(first), len(second))
|
||||
}
|
||||
if first[0].Template.Game.Artwork.Logo != "assets/icon.png" || first[0].Template.Game.Artwork.Image != "assets/banner.jpg" {
|
||||
t.Fatalf("catalog artwork = %#v", first[0].Template.Game.Artwork)
|
||||
|
||||
if len(first) < 2 {
|
||||
t.Fatalf("expected at least 2 built-in templates, got %d", len(first))
|
||||
}
|
||||
|
||||
secondByID := make(map[string]catalog.Snapshot, len(second))
|
||||
for _, snapshot := range second {
|
||||
secondByID[snapshot.Template.ID] = snapshot
|
||||
}
|
||||
|
||||
firstByID := make(map[string]catalog.Snapshot, len(first))
|
||||
for _, snapshot := range first {
|
||||
firstByID[snapshot.Template.ID] = snapshot
|
||||
|
||||
other, ok := secondByID[snapshot.Template.ID]
|
||||
if !ok {
|
||||
t.Fatalf("template %q missing from second catalog load", snapshot.Template.ID)
|
||||
}
|
||||
|
||||
if snapshot.Digest != other.Digest {
|
||||
t.Fatalf(
|
||||
"template %q digest is not deterministic: first=%q second=%q",
|
||||
snapshot.Template.ID,
|
||||
snapshot.Digest,
|
||||
other.Digest,
|
||||
)
|
||||
}
|
||||
|
||||
if snapshot.CanonicalYAML != other.CanonicalYAML {
|
||||
t.Fatalf("template %q canonical YAML is not deterministic", snapshot.Template.ID)
|
||||
}
|
||||
}
|
||||
|
||||
palworld, ok := firstByID["palworld-official"]
|
||||
if !ok {
|
||||
t.Fatal(`built-in template "palworld-official" is missing`)
|
||||
}
|
||||
|
||||
if palworld.Template.Game.Artwork.Logo != "assets/icon.png" ||
|
||||
palworld.Template.Game.Artwork.Image != "assets/banner.jpg" ||
|
||||
palworld.Template.Game.Artwork.Poster != "assets/poster.jpg" {
|
||||
t.Fatalf("palworld artwork = %#v", palworld.Template.Game.Artwork)
|
||||
}
|
||||
|
||||
vrising, ok := firstByID["vrising-didstopia"]
|
||||
if !ok {
|
||||
t.Fatal(`built-in template "vrising-didstopia" is missing`)
|
||||
}
|
||||
|
||||
if vrising.Template.Game.ID != "vrising" {
|
||||
t.Fatalf("vrising game id = %q", vrising.Template.Game.ID)
|
||||
}
|
||||
|
||||
if vrising.Template.Game.Artwork.Logo != "assets/icon.png" ||
|
||||
vrising.Template.Game.Artwork.Image != "assets/banner.jpg" ||
|
||||
vrising.Template.Game.Artwork.Poster != "assets/poster.jpg" {
|
||||
t.Fatalf("vrising artwork = %#v", vrising.Template.Game.Artwork)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,15 +89,18 @@ func TestCrossValidationRejectsMissingArtworkAsset(t *testing.T) {
|
||||
}
|
||||
body = []byte(strings.Replace(string(body), "image: assets/banner.jpg", "image: assets/missing.jpg", 1))
|
||||
_, err = catalog.Validate(body, "palworld", catalogdata.Files)
|
||||
|
||||
var validation *catalog.ValidationErrors
|
||||
if !errors.As(err, &validation) {
|
||||
t.Fatalf("validation error = %#v", err)
|
||||
}
|
||||
|
||||
for _, issue := range validation.Issues {
|
||||
if issue.Path == "/game/artwork/image" {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("issues = %#v", validation.Issues)
|
||||
}
|
||||
|
||||
@@ -52,10 +111,12 @@ func TestSchemaErrorsContainFieldPathAndLine(t *testing.T) {
|
||||
}
|
||||
body = []byte(strings.Replace(string(body), "schema_version: 1", "schema_version: 2", 1))
|
||||
_, err = catalog.Validate(body, "palworld", catalogdata.Files)
|
||||
|
||||
var validation *catalog.ValidationErrors
|
||||
if !errors.As(err, &validation) || len(validation.Issues) == 0 {
|
||||
t.Fatalf("validation error = %#v", err)
|
||||
}
|
||||
|
||||
if validation.Issues[0].Path == "" || validation.Issues[0].Line == 0 {
|
||||
t.Fatalf("validation issue = %#v", validation.Issues[0])
|
||||
}
|
||||
@@ -66,17 +127,27 @@ func TestCrossValidationRejectsUnknownBackupMount(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
normalized := strings.ReplaceAll(string(body), "\r\n", "\n")
|
||||
body = []byte(strings.Replace(normalized, " - saved\n restart_after_backup", " - missing\n restart_after_backup", 1))
|
||||
body = []byte(strings.Replace(
|
||||
normalized,
|
||||
" - saved\n restart_after_backup",
|
||||
" - missing\n restart_after_backup",
|
||||
1,
|
||||
))
|
||||
|
||||
_, err = catalog.Validate(body, "palworld", catalogdata.Files)
|
||||
|
||||
var validation *catalog.ValidationErrors
|
||||
if !errors.As(err, &validation) {
|
||||
t.Fatalf("validation error = %#v", err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, issue := range validation.Issues {
|
||||
found = found || issue.Path == "/backup/source_mounts"
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("issues = %#v", validation.Issues)
|
||||
}
|
||||
|
||||
@@ -21,22 +21,20 @@
|
||||
"game": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id", "name", "description", "image", "artwork"],
|
||||
"required": ["id", "name", "description", "artwork"],
|
||||
"properties": {
|
||||
"id": { "$ref": "#/$defs/id" },
|
||||
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
|
||||
"description": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
||||
"image": { "type": "string", "format": "uri", "pattern": "^https://", "maxLength": 2000 },
|
||||
"website": { "type": "string", "format": "uri", "pattern": "^https://" },
|
||||
"artwork": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["logo", "image", "attribution"],
|
||||
"required": ["logo", "image", "poster", "attribution"],
|
||||
"properties": {
|
||||
"logo": { "$ref": "#/$defs/relativeAssetPath" },
|
||||
"image": { "$ref": "#/$defs/relativeAssetPath" },
|
||||
"poster_source_url": { "type": "string", "format": "uri", "pattern": "^https://" },
|
||||
"logo_source_url": { "type": "string", "format": "uri", "pattern": "^https://" },
|
||||
"poster": { "$ref": "#/$defs/relativeAssetPath" },
|
||||
"attribution": { "type": "string", "maxLength": 500 }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user