@pippit-dev/cli 1.0.32 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  package generate_image
2
2
 
3
3
  import (
4
+ "encoding/json"
4
5
  "strings"
5
6
  "testing"
6
7
  )
@@ -19,10 +20,10 @@ func TestValidateOptionsRequiresModel(t *testing.T) {
19
20
  }
20
21
  }
21
22
 
22
- func TestValidateOptionsAllowsServerDecidedModel(t *testing.T) {
23
+ func TestValidateOptionsAllowsDynamicModelName(t *testing.T) {
23
24
  opts := &Options{
24
25
  Prompt: "x",
25
- Model: "seedream_3.0",
26
+ Model: "图片测试模型",
26
27
  }
27
28
 
28
29
  if err := ValidateOptions(opts); err != nil {
@@ -33,7 +34,7 @@ func TestValidateOptionsAllowsServerDecidedModel(t *testing.T) {
33
34
  func TestValidateOptionsAllowsServerDecidedRatio(t *testing.T) {
34
35
  opts := &Options{
35
36
  Prompt: "x",
36
- Model: "seedream_4.5",
37
+ Model: "图片测试模型",
37
38
  Ratio: "99",
38
39
  }
39
40
 
@@ -45,7 +46,7 @@ func TestValidateOptionsAllowsServerDecidedRatio(t *testing.T) {
45
46
  func TestValidateOptionsAllowsServerDecidedResolution(t *testing.T) {
46
47
  opts := &Options{
47
48
  Prompt: "x",
48
- Model: "seedream_4.5",
49
+ Model: "图片测试模型",
49
50
  Resolution: "8K",
50
51
  }
51
52
 
@@ -58,7 +59,7 @@ func TestValidateOptionsRejectsNegativeGenerateImageCount(t *testing.T) {
58
59
  count := -1
59
60
  opts := &Options{
60
61
  Prompt: "x",
61
- Model: "seedream_4.5",
62
+ Model: "图片测试模型",
62
63
  GenerateImageCount: &count,
63
64
  }
64
65
 
@@ -83,6 +84,13 @@ func TestParseRatioSupportsVisibleEnumValues(t *testing.T) {
83
84
  {ratio: "4", want: 4},
84
85
  {ratio: "5", want: 5},
85
86
  {ratio: "6", want: 6},
87
+ {ratio: "7", want: 7},
88
+ {ratio: "8", want: 8},
89
+ {ratio: "9", want: 9},
90
+ {ratio: "10", want: 10},
91
+ {ratio: "11", want: 11},
92
+ {ratio: "12", want: 12},
93
+ {ratio: "99", want: 99},
86
94
  }
87
95
 
88
96
  for _, tt := range cases {
@@ -98,20 +106,55 @@ func TestParseRatioSupportsVisibleEnumValues(t *testing.T) {
98
106
  }
99
107
  }
100
108
 
101
- func TestParseRatioRejectsNonInteger(t *testing.T) {
102
- _, err := parseRatio("1:1")
103
- if err == nil {
104
- t.Fatal("parseRatio() error = nil, want integer validation")
109
+ func TestImageOptionalParametersAndEffortPassThrough(t *testing.T) {
110
+ for _, effort := range []string{"", " HIGH ", "future-effort"} {
111
+ opts := &Options{Prompt: "image", Model: "未来图片模型", Effort: effort}
112
+ if err := ValidateOptions(opts); err != nil {
113
+ t.Fatal(err)
114
+ }
115
+ raw, err := json.Marshal(buildSubmitRunBody(opts, "future-image-model", nil))
116
+ if err != nil {
117
+ t.Fatal(err)
118
+ }
119
+ var body struct {
120
+ Settings map[string]json.RawMessage `json:"general_agent_settings"`
121
+ }
122
+ if err := json.Unmarshal(raw, &body); err != nil {
123
+ t.Fatal(err)
124
+ }
125
+ for _, key := range []string{"ratio", "resolution", "generate_image_count"} {
126
+ if _, exists := body.Settings[key]; exists {
127
+ t.Fatalf("unspecified %s was filled: %s", key, raw)
128
+ }
129
+ }
130
+ if effort == "" {
131
+ if _, exists := body.Settings["image_effort"]; exists {
132
+ t.Fatalf("unspecified effort was filled: %s", raw)
133
+ }
134
+ } else {
135
+ var got string
136
+ if err := json.Unmarshal(body.Settings["image_effort"], &got); err != nil || got != strings.ToLower(strings.TrimSpace(effort)) {
137
+ t.Fatalf("effort not forwarded: %s, %v", raw, err)
138
+ }
139
+ }
105
140
  }
106
- if !strings.Contains(err.Error(), `ratio "1:1" 必须是整数枚举值`) {
107
- t.Fatalf("error = %q, want integer validation", err)
141
+ }
142
+
143
+ func TestParseRatioRejectsNonInteger(t *testing.T) {
144
+ for _, ratio := range []string{"9:16", "adaptive", "17:11", "3.0"} {
145
+ t.Run(ratio, func(t *testing.T) {
146
+ err := ValidateOptions(&Options{Prompt: "image", Model: "未来图片模型", Ratio: ratio})
147
+ if err == nil || !strings.Contains(err.Error(), "必须是整数枚举值") {
148
+ t.Fatalf("ratio %q: error = %v, want integer enum validation", ratio, err)
149
+ }
150
+ })
108
151
  }
109
152
  }
110
153
 
111
154
  func TestValidateOptionsRejectsUnsupportedImageExtension(t *testing.T) {
112
155
  opts := &Options{
113
156
  Prompt: "x",
114
- Model: "seedream_4.5",
157
+ Model: "图片测试模型",
115
158
  ImagePaths: []string{"ref.tiff"},
116
159
  }
117
160
 
@@ -128,13 +171,13 @@ func TestBuildSubmitRunBodyWithGeneralAgentSettings(t *testing.T) {
128
171
  count := 2
129
172
  opts := &Options{
130
173
  Prompt: " 生成小猫海报 ",
131
- Model: " seedream_5.0_pro ",
174
+ Model: " Seedream 5.0 Pro ",
132
175
  Ratio: "6",
133
176
  Resolution: " 4k ",
134
177
  GenerateImageCount: &count,
135
178
  }
136
179
 
137
- body := buildSubmitRunBody(opts, []string{"asset_1"})
180
+ body := buildSubmitRunBody(opts, "seedream_5.0_pro", []string{"asset_1"})
138
181
  if body["agent_name"] != agentNameNest {
139
182
  t.Fatalf("agent_name = %v, want nest agent", body["agent_name"])
140
183
  }
@@ -6,17 +6,15 @@ import (
6
6
  "slices"
7
7
  "strconv"
8
8
  "strings"
9
- )
10
9
 
11
- // Mirrors capcut_business_common.Ratio and the Skill API's skillVideoRatio.
12
- // Custom (1) has no corresponding generate-video --ratio value.
13
- var ratioValues = map[int64]string{
14
- 0: "adaptive", 1: "", 2: "16:9", 3: "9:16", 4: "4:3", 5: "3:4", 6: "1:1",
15
- 7: "2:1", 8: "2.35:1", 9: "1.85:1", 10: "1.125:2.436", 11: "3:2", 12: "2:3", 13: "21:9",
16
- }
10
+ "github.com/Pippit-dev/pippit-cli/internal/common"
11
+ )
17
12
 
18
13
  type dimensionConfig struct {
19
14
  Key string `json:"key"`
15
+ Label string `json:"label"`
16
+ Description string `json:"description"`
17
+ Required *bool `json:"required_field"`
20
18
  DefaultValue *string `json:"default_value"`
21
19
  OptionList []struct {
22
20
  Value string `json:"value"`
@@ -36,6 +34,8 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
36
34
  var fields map[string]json.RawMessage
37
35
  var source struct {
38
36
  Key string `json:"key"`
37
+ Name string `json:"name"`
38
+ Kind string `json:"kind"`
39
39
  Ratios []int64 `json:"supported_ratio_list"`
40
40
  Default *int64 `json:"default_ratio"`
41
41
  Parameter struct {
@@ -57,18 +57,27 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
57
57
  }
58
58
  delete(out, "config_key")
59
59
  delete(out, "is_default") // A server default does not authorize model selection.
60
+ if source.Kind == "image" {
61
+ // Billing maps and other Web-only metadata can contain wire model names.
62
+ // Keep them in the raw catalog, not in host-facing command output.
63
+ out = map[string]any{"name": strings.TrimSpace(source.Name), "kind": source.Kind}
64
+ for _, key := range []string{"description", "parameter_config", "creation_mode_config"} {
65
+ if value, exists := fields[key]; exists {
66
+ out[key] = value
67
+ }
68
+ }
69
+ }
60
70
  warnings := []string{}
61
71
  warn := func(message string) { warnings = append(warnings, message) }
62
- ratios := make([]string, 0, len(source.Ratios))
72
+ ratios := make([]any, 0, len(source.Ratios))
63
73
  for _, value := range source.Ratios {
64
- // Unknown enums and Custom cannot be submitted as CLI ratio strings.
65
- if value := ratioValues[value]; value != "" && !slices.Contains(ratios, value) {
74
+ if value, ok := cliRatioValue(value, source.Kind); ok && !slices.Contains(ratios, value) {
66
75
  ratios = append(ratios, value)
67
76
  }
68
77
  }
69
78
  ratio := map[string]any{"options": ratios}
70
79
  if source.Default != nil {
71
- if value := ratioValues[*source.Default]; value != "" {
80
+ if value, ok := cliRatioValue(*source.Default, source.Kind); ok {
72
81
  if slices.Contains(ratios, value) {
73
82
  ratio["default"] = value
74
83
  } else {
@@ -84,17 +93,37 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
84
93
 
85
94
  seen := make(map[string]bool)
86
95
  for _, dimension := range source.Parameter.Dimensions {
87
- if dimension == nil || (dimension.Key != "resolution" && dimension.Key != "duration") {
96
+ if dimension == nil {
97
+ continue
98
+ }
99
+ if source.Kind == "image" {
100
+ if dimension.Key != "resolution" && dimension.Key != "ratio" && dimension.Key != "effort" {
101
+ continue
102
+ }
103
+ } else if dimension.Key != "resolution" && dimension.Key != "duration" {
88
104
  continue
89
105
  }
90
106
  if seen[dimension.Key] {
91
107
  return nil, fmt.Errorf("模型配置包含重复 %s 维度,请 --refresh 重试", dimension.Key)
92
108
  }
93
109
  seen[dimension.Key] = true
94
- out[dimension.Key] = describeDimension(dimension, warn)
110
+ out[dimension.Key] = describeDimension(dimension, source.Kind, warn)
111
+ }
112
+ if source.Kind == "image" {
113
+ if imageRatio, ok := out["ratio"].(map[string]any); ok {
114
+ if options, ok := imageRatio["options"].([]any); ok {
115
+ labels := make(map[string]string, len(options))
116
+ for _, option := range options {
117
+ value := option.(int64)
118
+ labels[strconv.FormatInt(value, 10)] = common.RatioValue(value)
119
+ }
120
+ imageRatio["option_labels"] = labels
121
+ }
122
+ }
95
123
  }
96
- // Keep unrelated parameter dimensions and combination constraints as supplied.
97
- if parameterRaw, ok := fields["parameter_config"]; ok && string(parameterRaw) != "null" {
124
+ // Image dimensions retain their complete source metadata and combination rules.
125
+ // Video descriptions keep the existing compact representation.
126
+ if parameterRaw, ok := fields["parameter_config"]; source.Kind != "image" && ok && string(parameterRaw) != "null" {
98
127
  var parameter map[string]json.RawMessage
99
128
  if err := json.Unmarshal(parameterRaw, &parameter); err != nil {
100
129
  return nil, err
@@ -146,7 +175,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
146
175
  if len(limits) > 0 {
147
176
  out["material_limits"] = limits
148
177
  }
149
- if source.Creation != nil {
178
+ if source.Creation != nil && source.Kind != "image" {
150
179
  modes := make([]map[string]any, 0, len(source.Creation.Modes))
151
180
  for _, rawMode := range source.Creation.Modes {
152
181
  if string(rawMode) == "null" {
@@ -181,7 +210,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
181
210
  case "", "inherit":
182
211
  entry["ratio"] = ratio
183
212
  if mode.Key == "text_to_video" && (source.Key == "MiniMax-H3" || source.Key == "MiniMax-H3-Max") {
184
- fixed := make([]string, 0, len(ratios))
213
+ fixed := make([]any, 0, len(ratios))
185
214
  for _, value := range ratios {
186
215
  if value != "adaptive" {
187
216
  fixed = append(fixed, value)
@@ -210,7 +239,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
210
239
  }
211
240
  out["creation_modes"] = modes
212
241
  }
213
- if (source.Key == "MiniMax-H3" || source.Key == "MiniMax-H3-Max") && slices.Contains(ratios, "adaptive") {
242
+ if source.Kind != "image" && (source.Key == "MiniMax-H3" || source.Key == "MiniMax-H3-Max") && slices.Contains(ratios, "adaptive") {
214
243
  out["notes"] = []string{"MiniMax 使用 adaptive 需要参考图片或视频;纯文生视频请选择固定比例。创作模式的比例策略优先于模型级选项。"}
215
244
  }
216
245
  if len(warnings) > 0 {
@@ -219,8 +248,29 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
219
248
  return json.Marshal(out)
220
249
  }
221
250
 
222
- func describeDimension(d *dimensionConfig, warn func(string)) map[string]any {
251
+ // Image generation accepts wire enums; video generation accepts ratio strings.
252
+ func cliRatioValue(value int64, kind string) (any, bool) {
253
+ label := common.RatioValue(value)
254
+ if label == "" {
255
+ return nil, false
256
+ }
257
+ if kind == "image" {
258
+ return value, true
259
+ }
260
+ return label, true
261
+ }
262
+
263
+ func describeDimension(d *dimensionConfig, kind string, warn func(string)) map[string]any {
223
264
  out := map[string]any{}
265
+ if d.Label != "" {
266
+ out["label"] = d.Label
267
+ }
268
+ if d.Description != "" {
269
+ out["description"] = d.Description
270
+ }
271
+ if d.Required != nil {
272
+ out["required_field"] = *d.Required
273
+ }
224
274
  if len(d.ActiveWhenAny) > 0 {
225
275
  out["active_when_any"] = d.ActiveWhenAny
226
276
  }
@@ -230,6 +280,16 @@ func describeDimension(d *dimensionConfig, warn func(string)) map[string]any {
230
280
  number, err := strconv.ParseInt(value, 10, 32)
231
281
  return number, err == nil && number > 0
232
282
  }
283
+ if d.Key == "ratio" {
284
+ number, err := strconv.ParseInt(value, 10, 64)
285
+ if err != nil {
286
+ return nil, false
287
+ }
288
+ return cliRatioValue(number, kind)
289
+ }
290
+ if kind == "image" && d.Key == "resolution" {
291
+ return strings.ToUpper(value), value != ""
292
+ }
233
293
  return strings.ToLower(value), value != ""
234
294
  }
235
295
  if d.Key == "duration" {
@@ -21,12 +21,12 @@ func description(t *testing.T, raw string) map[string]json.RawMessage {
21
21
  }
22
22
 
23
23
  func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
24
- raw := `{"key":"MiniMax-H3","is_default":true,"supported_ratio_list":[0,2,13,3,4,5,6],"default_ratio":3,"config_key":"internal",
24
+ raw := `{"key":"MiniMax-H3","kind":"video","is_default":true,"supported_ratio_list":[0,2,13,3,4,5,6],"default_ratio":3,"config_key":"internal",
25
25
  "future_field":9007199254740993,"audio_total_limit":0,"max_image_size":31457280,"min_video_duration":2000,
26
26
  "supported_duration_list":[{"value":999}],"default_duration_value":999,
27
27
  "parameter_config":{"dimensions":[
28
- {"key":"duration","default_value":"10","range_config":{"min_value":4,"max_value":15,"step":1}},
29
- {"key":"resolution","default_value":" 768P ","option_list":[{"value":"768p"},{"value":"2k"},{"value":"4k","disabled":true}]},
28
+ {"key":"duration","label":"视频时长","description":"生成的视频时长","required_field":true,"default_value":"10","range_config":{"min_value":4,"max_value":15,"step":1}},
29
+ {"key":"resolution","label":"视频分辨率","description":"输出清晰度","required_field":false,"default_value":" 768P ","option_list":[{"value":"768p"},{"value":"2k"},{"value":"4k","disabled":true}]},
30
30
  {"key":"seed","default_value":"random"}],"need_available_combinations":true}}
31
31
  `
32
32
  out := description(t, raw)
@@ -46,8 +46,10 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
46
46
  t.Fatalf("ratio=%+v", ratio)
47
47
  }
48
48
  var resolution struct {
49
- Options []string
50
- Default string
49
+ Options []string
50
+ Default string
51
+ Label, Description string
52
+ Required *bool `json:"required_field"`
51
53
  }
52
54
  if err := json.Unmarshal(out["resolution"], &resolution); err != nil {
53
55
  t.Fatal(err)
@@ -55,9 +57,14 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
55
57
  if !reflect.DeepEqual(resolution.Options, []string{"768p", "2k"}) || resolution.Default != "768p" {
56
58
  t.Fatalf("resolution=%+v", resolution)
57
59
  }
60
+ if resolution.Label != "视频分辨率" || resolution.Description != "输出清晰度" || resolution.Required == nil || *resolution.Required {
61
+ t.Fatalf("video resolution metadata lost: %+v", resolution)
62
+ }
58
63
  var duration struct {
59
64
  Min, Max, Step, Default int
60
65
  Unit string
66
+ Label, Description string
67
+ Required *bool `json:"required_field"`
61
68
  }
62
69
  if err := json.Unmarshal(out["duration"], &duration); err != nil {
63
70
  t.Fatal(err)
@@ -65,6 +72,9 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
65
72
  if duration.Min != 4 || duration.Max != 15 || duration.Step != 1 || duration.Default != 10 || duration.Unit != "seconds" {
66
73
  t.Fatalf("duration=%+v", duration)
67
74
  }
75
+ if duration.Label != "视频时长" || duration.Description != "生成的视频时长" || duration.Required == nil || !*duration.Required {
76
+ t.Fatalf("video duration metadata lost: %+v", duration)
77
+ }
68
78
  if string(out["future_field"]) != "9007199254740993" {
69
79
  t.Fatal("integer precision lost")
70
80
  }
@@ -197,3 +207,119 @@ func TestDescriptionCreationModesAndCacheNotMutated(t *testing.T) {
197
207
  t.Fatal("description mutated cached raw config")
198
208
  }
199
209
  }
210
+
211
+ func TestImageDescriptionParametersAndConstraints(t *testing.T) {
212
+ raw := `{"key":"future-image","name":"未来图片模型","report_name":"internal-report-model","kind":"image","is_default":true,
213
+ "supported_ratio_list":[0,2,6,13,1,999],"default_ratio":6,
214
+ "parameter_config":{"dimensions":[
215
+ {"key":"resolution","label":"图片分辨率","required_field":false,"default_value":"2k","option_list":[{"value":"2k","label":"高清"},{"value":"4K","disabled":true}]},
216
+ {"key":"effort","label":"推理强度","description":"计算档位","required_field":true,"default_value":"HIGH","active_when_any":[{"resolution":["2k"]}],"option_list":[{"value":"low"},{"value":"HIGH","label":"高","description":"更多计算"},{"value":"max","disabled":true}]},
217
+ {"key":"future-dimension","option_list":[{"value":"future-choice"}]}],
218
+ "need_available_combinations":true,"combination_dimension_keys":["resolution","effort"],
219
+ "default_combination":{"resolution":"2k","effort":"HIGH"},
220
+ "available_combinations":[{"option_values":{"resolution":"2k","effort":"HIGH"}}]},
221
+ "creation_mode_config":{"modes":[{"key":"reference_generation","enabled":true}]}}
222
+ `
223
+ out := description(t, raw)
224
+ if _, exists := out["key"]; exists || string(out["name"]) != `"未来图片模型"` {
225
+ t.Fatalf("image identity must contain only the display name: %s", out)
226
+ }
227
+ if _, exists := out["report_name"]; exists {
228
+ t.Fatal("internal reporting model name exposed")
229
+ }
230
+ var ratio struct {
231
+ Options []int64
232
+ Default int64
233
+ Labels map[string]string `json:"option_labels"`
234
+ }
235
+ var resolution, effort struct {
236
+ Options []string
237
+ Default string
238
+ Required *bool `json:"required_field"`
239
+ Active json.RawMessage `json:"active_when_any"`
240
+ Label string
241
+ Description string
242
+ }
243
+ for key, target := range map[string]any{"ratio": &ratio, "resolution": &resolution, "effort": &effort} {
244
+ if err := json.Unmarshal(out[key], target); err != nil {
245
+ t.Fatal(err)
246
+ }
247
+ }
248
+ if !reflect.DeepEqual(ratio.Options, []int64{0, 2, 6, 13}) || ratio.Default != 6 || !reflect.DeepEqual(ratio.Labels, map[string]string{"0": "adaptive", "2": "16:9", "6": "1:1", "13": "21:9"}) {
249
+ t.Fatalf("ratio=%+v", ratio)
250
+ }
251
+ if !reflect.DeepEqual(resolution.Options, []string{"2K"}) || resolution.Default != "2K" || resolution.Required == nil || *resolution.Required {
252
+ t.Fatalf("resolution=%+v", resolution)
253
+ }
254
+ if !reflect.DeepEqual(effort.Options, []string{"low", "high"}) || effort.Default != "high" || effort.Required == nil || !*effort.Required || effort.Label != "推理强度" || effort.Description != "计算档位" || string(effort.Active) != `[{"resolution":["2k"]}]` {
255
+ t.Fatalf("effort=%+v", effort)
256
+ }
257
+ var source map[string]json.RawMessage
258
+ if err := json.Unmarshal([]byte(raw), &source); err != nil {
259
+ t.Fatal(err)
260
+ }
261
+ var compact bytes.Buffer
262
+ if err := json.Compact(&compact, source["parameter_config"]); err != nil {
263
+ t.Fatal(err)
264
+ }
265
+ if !bytes.Equal(out["parameter_config"], compact.Bytes()) {
266
+ t.Fatalf("raw dimension metadata or combination rules changed: %s", out["parameter_config"])
267
+ }
268
+ if bytes.Contains(out["creation_mode_config"], []byte("generate_type")) || len(out["creation_modes"]) != 0 {
269
+ t.Fatal("image modes must not acquire video generation selectors")
270
+ }
271
+ if _, exists := out["is_default"]; exists {
272
+ t.Fatal("model default marker exposed")
273
+ }
274
+ }
275
+
276
+ func TestImageDescriptionOptionalEffortAndRatioDimension(t *testing.T) {
277
+ out := description(t, `{"kind":"image","supported_ratio_list":[2],"parameter_config":{"dimensions":[{"key":"ratio","default_value":"13","option_list":[{"value":"13"},{"value":"6"},{"value":"2","disabled":true},{"value":"999"},{"value":"1"}]}]}}`)
278
+ if len(out["effort"]) != 0 || len(out["resolution"]) != 0 {
279
+ t.Fatal("absent capabilities must not be invented")
280
+ }
281
+ var ratio struct {
282
+ Options []int64
283
+ Default int64
284
+ Labels map[string]string `json:"option_labels"`
285
+ }
286
+ if err := json.Unmarshal(out["ratio"], &ratio); err != nil {
287
+ t.Fatal(err)
288
+ }
289
+ if !reflect.DeepEqual(ratio.Options, []int64{13, 6}) || ratio.Default != 13 || !reflect.DeepEqual(ratio.Labels, map[string]string{"13": "21:9", "6": "1:1"}) {
290
+ t.Fatalf("ratio dimension did not override legacy list: %+v", ratio)
291
+ }
292
+ for _, raw := range []string{
293
+ `{"kind":"image","parameter_config":{"dimensions":[{"key":"effort","default_value":"high","option_list":[{"value":"low"},{"value":"high","disabled":true}]}]}}`,
294
+ `{"kind":"image","parameter_config":{"dimensions":[{"key":"effort","option_list":[]}]}}`,
295
+ } {
296
+ out := description(t, raw)
297
+ if len(out["warnings"]) == 0 || bytes.Contains(out["effort"], []byte(`"default"`)) {
298
+ t.Fatalf("invalid effort configuration accepted: %s", out["effort"])
299
+ }
300
+ }
301
+ _, err := describeModel(json.RawMessage(`{"kind":"image","parameter_config":{"dimensions":[{"key":"effort"},{"key":"effort"}]}}`))
302
+ if err == nil {
303
+ t.Fatal("duplicate effort dimension accepted")
304
+ }
305
+ }
306
+
307
+ func TestImageRatioZeroDefaultAndUnavailableValues(t *testing.T) {
308
+ for _, raw := range []string{
309
+ `{"kind":"image","supported_ratio_list":[0,0,3,1,999],"default_ratio":0}`,
310
+ `{"kind":"image","parameter_config":{"dimensions":[{"key":"ratio","default_value":"0","option_list":[{"value":"0"},{"value":"0"},{"value":"3"},{"value":"6","disabled":true},{"value":"1"},{"value":"999"}]}]}}`,
311
+ } {
312
+ out := description(t, raw)
313
+ var ratio struct {
314
+ Options []int64
315
+ Default *int64
316
+ Labels map[string]string `json:"option_labels"`
317
+ }
318
+ if err := json.Unmarshal(out["ratio"], &ratio); err != nil {
319
+ t.Fatal(err)
320
+ }
321
+ if !reflect.DeepEqual(ratio.Options, []int64{0, 3}) || ratio.Default == nil || *ratio.Default != 0 || !reflect.DeepEqual(ratio.Labels, map[string]string{"0": "adaptive", "3": "9:16"}) {
322
+ t.Fatalf("numeric zero default or available ratio values changed: %s", out["ratio"])
323
+ }
324
+ }
325
+ }