Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package template
  4. import (
  5. "net/url"
  6. "testing"
  7. "code.gitea.io/gitea/modules/json"
  8. api "code.gitea.io/gitea/modules/structs"
  9. "github.com/stretchr/testify/require"
  10. )
  11. func TestValidate(t *testing.T) {
  12. tests := []struct {
  13. name string
  14. filename string
  15. content string
  16. want *api.IssueTemplate
  17. wantErr string
  18. }{
  19. {
  20. name: "miss name",
  21. content: ``,
  22. wantErr: "'name' is required",
  23. },
  24. {
  25. name: "miss about",
  26. content: `
  27. name: "test"
  28. `,
  29. wantErr: "'about' is required",
  30. },
  31. {
  32. name: "miss body",
  33. content: `
  34. name: "test"
  35. about: "this is about"
  36. `,
  37. wantErr: "'body' is required",
  38. },
  39. {
  40. name: "markdown miss value",
  41. content: `
  42. name: "test"
  43. about: "this is about"
  44. body:
  45. - type: "markdown"
  46. `,
  47. wantErr: "body[0](markdown): 'value' is required",
  48. },
  49. {
  50. name: "markdown invalid value",
  51. content: `
  52. name: "test"
  53. about: "this is about"
  54. body:
  55. - type: "markdown"
  56. attributes:
  57. value: true
  58. `,
  59. wantErr: "body[0](markdown): 'value' should be a string",
  60. },
  61. {
  62. name: "markdown empty value",
  63. content: `
  64. name: "test"
  65. about: "this is about"
  66. body:
  67. - type: "markdown"
  68. attributes:
  69. value: ""
  70. `,
  71. wantErr: "body[0](markdown): 'value' is required",
  72. },
  73. {
  74. name: "textarea invalid id",
  75. content: `
  76. name: "test"
  77. about: "this is about"
  78. body:
  79. - type: "textarea"
  80. id: "?"
  81. `,
  82. wantErr: "body[0](textarea): 'id' should contain only alphanumeric, '-' and '_'",
  83. },
  84. {
  85. name: "textarea miss label",
  86. content: `
  87. name: "test"
  88. about: "this is about"
  89. body:
  90. - type: "textarea"
  91. id: "1"
  92. `,
  93. wantErr: "body[0](textarea): 'label' is required",
  94. },
  95. {
  96. name: "textarea conflict id",
  97. content: `
  98. name: "test"
  99. about: "this is about"
  100. body:
  101. - type: "textarea"
  102. id: "1"
  103. attributes:
  104. label: "a"
  105. - type: "textarea"
  106. id: "1"
  107. attributes:
  108. label: "b"
  109. `,
  110. wantErr: "body[1](textarea): 'id' should be unique",
  111. },
  112. {
  113. name: "textarea invalid description",
  114. content: `
  115. name: "test"
  116. about: "this is about"
  117. body:
  118. - type: "textarea"
  119. id: "1"
  120. attributes:
  121. label: "a"
  122. description: true
  123. `,
  124. wantErr: "body[0](textarea): 'description' should be a string",
  125. },
  126. {
  127. name: "textarea invalid required",
  128. content: `
  129. name: "test"
  130. about: "this is about"
  131. body:
  132. - type: "textarea"
  133. id: "1"
  134. attributes:
  135. label: "a"
  136. validations:
  137. required: "on"
  138. `,
  139. wantErr: "body[0](textarea): 'required' should be a bool",
  140. },
  141. {
  142. name: "input invalid description",
  143. content: `
  144. name: "test"
  145. about: "this is about"
  146. body:
  147. - type: "input"
  148. id: "1"
  149. attributes:
  150. label: "a"
  151. description: true
  152. `,
  153. wantErr: "body[0](input): 'description' should be a string",
  154. },
  155. {
  156. name: "input invalid is_number",
  157. content: `
  158. name: "test"
  159. about: "this is about"
  160. body:
  161. - type: "input"
  162. id: "1"
  163. attributes:
  164. label: "a"
  165. validations:
  166. is_number: "yes"
  167. `,
  168. wantErr: "body[0](input): 'is_number' should be a bool",
  169. },
  170. {
  171. name: "input invalid regex",
  172. content: `
  173. name: "test"
  174. about: "this is about"
  175. body:
  176. - type: "input"
  177. id: "1"
  178. attributes:
  179. label: "a"
  180. validations:
  181. regex: true
  182. `,
  183. wantErr: "body[0](input): 'regex' should be a string",
  184. },
  185. {
  186. name: "dropdown invalid description",
  187. content: `
  188. name: "test"
  189. about: "this is about"
  190. body:
  191. - type: "dropdown"
  192. id: "1"
  193. attributes:
  194. label: "a"
  195. description: true
  196. `,
  197. wantErr: "body[0](dropdown): 'description' should be a string",
  198. },
  199. {
  200. name: "dropdown invalid multiple",
  201. content: `
  202. name: "test"
  203. about: "this is about"
  204. body:
  205. - type: "dropdown"
  206. id: "1"
  207. attributes:
  208. label: "a"
  209. multiple: "on"
  210. `,
  211. wantErr: "body[0](dropdown): 'multiple' should be a bool",
  212. },
  213. {
  214. name: "checkboxes invalid description",
  215. content: `
  216. name: "test"
  217. about: "this is about"
  218. body:
  219. - type: "checkboxes"
  220. id: "1"
  221. attributes:
  222. label: "a"
  223. description: true
  224. `,
  225. wantErr: "body[0](checkboxes): 'description' should be a string",
  226. },
  227. {
  228. name: "invalid type",
  229. content: `
  230. name: "test"
  231. about: "this is about"
  232. body:
  233. - type: "video"
  234. id: "1"
  235. attributes:
  236. label: "a"
  237. `,
  238. wantErr: "body[0](video): unknown type",
  239. },
  240. {
  241. name: "dropdown miss options",
  242. content: `
  243. name: "test"
  244. about: "this is about"
  245. body:
  246. - type: "dropdown"
  247. id: "1"
  248. attributes:
  249. label: "a"
  250. `,
  251. wantErr: "body[0](dropdown): 'options' is required and should be a array",
  252. },
  253. {
  254. name: "dropdown invalid options",
  255. content: `
  256. name: "test"
  257. about: "this is about"
  258. body:
  259. - type: "dropdown"
  260. id: "1"
  261. attributes:
  262. label: "a"
  263. options:
  264. - "a"
  265. - true
  266. `,
  267. wantErr: "body[0](dropdown), option[1]: should be a string",
  268. },
  269. {
  270. name: "checkboxes invalid options",
  271. content: `
  272. name: "test"
  273. about: "this is about"
  274. body:
  275. - type: "checkboxes"
  276. id: "1"
  277. attributes:
  278. label: "a"
  279. options:
  280. - "a"
  281. - true
  282. `,
  283. wantErr: "body[0](checkboxes), option[0]: should be a dictionary",
  284. },
  285. {
  286. name: "checkboxes option miss label",
  287. content: `
  288. name: "test"
  289. about: "this is about"
  290. body:
  291. - type: "checkboxes"
  292. id: "1"
  293. attributes:
  294. label: "a"
  295. options:
  296. - required: true
  297. `,
  298. wantErr: "body[0](checkboxes), option[0]: 'label' is required and should be a string",
  299. },
  300. {
  301. name: "checkboxes option invalid required",
  302. content: `
  303. name: "test"
  304. about: "this is about"
  305. body:
  306. - type: "checkboxes"
  307. id: "1"
  308. attributes:
  309. label: "a"
  310. options:
  311. - label: "a"
  312. required: "on"
  313. `,
  314. wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
  315. },
  316. {
  317. name: "valid",
  318. content: `
  319. name: Name
  320. title: Title
  321. about: About
  322. labels: ["label1", "label2"]
  323. ref: Ref
  324. body:
  325. - type: markdown
  326. id: id1
  327. attributes:
  328. value: Value of the markdown
  329. - type: textarea
  330. id: id2
  331. attributes:
  332. label: Label of textarea
  333. description: Description of textarea
  334. placeholder: Placeholder of textarea
  335. value: Value of textarea
  336. render: bash
  337. validations:
  338. required: true
  339. - type: input
  340. id: id3
  341. attributes:
  342. label: Label of input
  343. description: Description of input
  344. placeholder: Placeholder of input
  345. value: Value of input
  346. validations:
  347. required: true
  348. is_number: true
  349. regex: "[a-zA-Z0-9]+"
  350. - type: dropdown
  351. id: id4
  352. attributes:
  353. label: Label of dropdown
  354. description: Description of dropdown
  355. multiple: true
  356. options:
  357. - Option 1 of dropdown
  358. - Option 2 of dropdown
  359. - Option 3 of dropdown
  360. validations:
  361. required: true
  362. - type: checkboxes
  363. id: id5
  364. attributes:
  365. label: Label of checkboxes
  366. description: Description of checkboxes
  367. options:
  368. - label: Option 1 of checkboxes
  369. required: true
  370. - label: Option 2 of checkboxes
  371. required: false
  372. - label: Option 3 of checkboxes
  373. required: true
  374. `,
  375. want: &api.IssueTemplate{
  376. Name: "Name",
  377. Title: "Title",
  378. About: "About",
  379. Labels: []string{"label1", "label2"},
  380. Ref: "Ref",
  381. Fields: []*api.IssueFormField{
  382. {
  383. Type: "markdown",
  384. ID: "id1",
  385. Attributes: map[string]interface{}{
  386. "value": "Value of the markdown",
  387. },
  388. },
  389. {
  390. Type: "textarea",
  391. ID: "id2",
  392. Attributes: map[string]interface{}{
  393. "label": "Label of textarea",
  394. "description": "Description of textarea",
  395. "placeholder": "Placeholder of textarea",
  396. "value": "Value of textarea",
  397. "render": "bash",
  398. },
  399. Validations: map[string]interface{}{
  400. "required": true,
  401. },
  402. },
  403. {
  404. Type: "input",
  405. ID: "id3",
  406. Attributes: map[string]interface{}{
  407. "label": "Label of input",
  408. "description": "Description of input",
  409. "placeholder": "Placeholder of input",
  410. "value": "Value of input",
  411. },
  412. Validations: map[string]interface{}{
  413. "required": true,
  414. "is_number": true,
  415. "regex": "[a-zA-Z0-9]+",
  416. },
  417. },
  418. {
  419. Type: "dropdown",
  420. ID: "id4",
  421. Attributes: map[string]interface{}{
  422. "label": "Label of dropdown",
  423. "description": "Description of dropdown",
  424. "multiple": true,
  425. "options": []interface{}{
  426. "Option 1 of dropdown",
  427. "Option 2 of dropdown",
  428. "Option 3 of dropdown",
  429. },
  430. },
  431. Validations: map[string]interface{}{
  432. "required": true,
  433. },
  434. },
  435. {
  436. Type: "checkboxes",
  437. ID: "id5",
  438. Attributes: map[string]interface{}{
  439. "label": "Label of checkboxes",
  440. "description": "Description of checkboxes",
  441. "options": []interface{}{
  442. map[string]interface{}{"label": "Option 1 of checkboxes", "required": true},
  443. map[string]interface{}{"label": "Option 2 of checkboxes", "required": false},
  444. map[string]interface{}{"label": "Option 3 of checkboxes", "required": true},
  445. },
  446. },
  447. },
  448. },
  449. FileName: "test.yaml",
  450. },
  451. wantErr: "",
  452. },
  453. {
  454. name: "single label",
  455. content: `
  456. name: Name
  457. title: Title
  458. about: About
  459. labels: label1
  460. ref: Ref
  461. body:
  462. - type: markdown
  463. id: id1
  464. attributes:
  465. value: Value of the markdown
  466. `,
  467. want: &api.IssueTemplate{
  468. Name: "Name",
  469. Title: "Title",
  470. About: "About",
  471. Labels: []string{"label1"},
  472. Ref: "Ref",
  473. Fields: []*api.IssueFormField{
  474. {
  475. Type: "markdown",
  476. ID: "id1",
  477. Attributes: map[string]interface{}{
  478. "value": "Value of the markdown",
  479. },
  480. },
  481. },
  482. FileName: "test.yaml",
  483. },
  484. wantErr: "",
  485. },
  486. {
  487. name: "comma-delimited labels",
  488. content: `
  489. name: Name
  490. title: Title
  491. about: About
  492. labels: label1,label2,,label3 ,,
  493. ref: Ref
  494. body:
  495. - type: markdown
  496. id: id1
  497. attributes:
  498. value: Value of the markdown
  499. `,
  500. want: &api.IssueTemplate{
  501. Name: "Name",
  502. Title: "Title",
  503. About: "About",
  504. Labels: []string{"label1", "label2", "label3"},
  505. Ref: "Ref",
  506. Fields: []*api.IssueFormField{
  507. {
  508. Type: "markdown",
  509. ID: "id1",
  510. Attributes: map[string]interface{}{
  511. "value": "Value of the markdown",
  512. },
  513. },
  514. },
  515. FileName: "test.yaml",
  516. },
  517. wantErr: "",
  518. },
  519. {
  520. name: "empty string as labels",
  521. content: `
  522. name: Name
  523. title: Title
  524. about: About
  525. labels: ''
  526. ref: Ref
  527. body:
  528. - type: markdown
  529. id: id1
  530. attributes:
  531. value: Value of the markdown
  532. `,
  533. want: &api.IssueTemplate{
  534. Name: "Name",
  535. Title: "Title",
  536. About: "About",
  537. Labels: nil,
  538. Ref: "Ref",
  539. Fields: []*api.IssueFormField{
  540. {
  541. Type: "markdown",
  542. ID: "id1",
  543. Attributes: map[string]interface{}{
  544. "value": "Value of the markdown",
  545. },
  546. },
  547. },
  548. FileName: "test.yaml",
  549. },
  550. wantErr: "",
  551. },
  552. {
  553. name: "comma delimited labels in markdown",
  554. filename: "test.md",
  555. content: `---
  556. name: Name
  557. title: Title
  558. about: About
  559. labels: label1,label2,,label3 ,,
  560. ref: Ref
  561. ---
  562. Content
  563. `,
  564. want: &api.IssueTemplate{
  565. Name: "Name",
  566. Title: "Title",
  567. About: "About",
  568. Labels: []string{"label1", "label2", "label3"},
  569. Ref: "Ref",
  570. Fields: nil,
  571. Content: "Content\n",
  572. FileName: "test.md",
  573. },
  574. wantErr: "",
  575. },
  576. }
  577. for _, tt := range tests {
  578. t.Run(tt.name, func(t *testing.T) {
  579. filename := "test.yaml"
  580. if tt.filename != "" {
  581. filename = tt.filename
  582. }
  583. tmpl, err := unmarshal(filename, []byte(tt.content))
  584. require.NoError(t, err)
  585. if tt.wantErr != "" {
  586. require.EqualError(t, Validate(tmpl), tt.wantErr)
  587. } else {
  588. require.NoError(t, Validate(tmpl))
  589. want, _ := json.Marshal(tt.want)
  590. got, _ := json.Marshal(tmpl)
  591. require.JSONEq(t, string(want), string(got))
  592. }
  593. })
  594. }
  595. }
  596. func TestRenderToMarkdown(t *testing.T) {
  597. type args struct {
  598. template string
  599. values url.Values
  600. }
  601. tests := []struct {
  602. name string
  603. args args
  604. want string
  605. }{
  606. {
  607. name: "normal",
  608. args: args{
  609. template: `
  610. name: Name
  611. title: Title
  612. about: About
  613. labels: ["label1", "label2"]
  614. ref: Ref
  615. body:
  616. - type: markdown
  617. id: id1
  618. attributes:
  619. value: Value of the markdown
  620. - type: textarea
  621. id: id2
  622. attributes:
  623. label: Label of textarea
  624. description: Description of textarea
  625. placeholder: Placeholder of textarea
  626. value: Value of textarea
  627. render: bash
  628. validations:
  629. required: true
  630. - type: input
  631. id: id3
  632. attributes:
  633. label: Label of input
  634. description: Description of input
  635. placeholder: Placeholder of input
  636. value: Value of input
  637. hide_label: true
  638. validations:
  639. required: true
  640. is_number: true
  641. regex: "[a-zA-Z0-9]+"
  642. - type: dropdown
  643. id: id4
  644. attributes:
  645. label: Label of dropdown
  646. description: Description of dropdown
  647. multiple: true
  648. options:
  649. - Option 1 of dropdown
  650. - Option 2 of dropdown
  651. - Option 3 of dropdown
  652. validations:
  653. required: true
  654. - type: checkboxes
  655. id: id5
  656. attributes:
  657. label: Label of checkboxes
  658. description: Description of checkboxes
  659. options:
  660. - label: Option 1 of checkboxes
  661. required: true
  662. - label: Option 2 of checkboxes
  663. required: false
  664. - label: Option 3 of checkboxes
  665. required: true
  666. `,
  667. values: map[string][]string{
  668. "form-field-id2": {"Value of id2"},
  669. "form-field-id3": {"Value of id3"},
  670. "form-field-id4": {"0,1"},
  671. "form-field-id5-0": {"on"},
  672. "form-field-id5-2": {"on"},
  673. },
  674. },
  675. want: `### Label of textarea
  676. ` + "```bash\nValue of id2\n```" + `
  677. Value of id3
  678. ### Label of dropdown
  679. Option 1 of dropdown, Option 2 of dropdown
  680. ### Label of checkboxes
  681. - [x] Option 1 of checkboxes
  682. - [ ] Option 2 of checkboxes
  683. - [x] Option 3 of checkboxes
  684. `,
  685. },
  686. }
  687. for _, tt := range tests {
  688. t.Run(tt.name, func(t *testing.T) {
  689. template, err := Unmarshal("test.yaml", []byte(tt.args.template))
  690. if err != nil {
  691. t.Fatal(err)
  692. }
  693. if got := RenderToMarkdown(template, tt.args.values); got != tt.want {
  694. t.Errorf("RenderToMarkdown() = %v, want %v", got, tt.want)
  695. }
  696. })
  697. }
  698. }
  699. func Test_minQuotes(t *testing.T) {
  700. type args struct {
  701. value string
  702. }
  703. tests := []struct {
  704. name string
  705. args args
  706. want string
  707. }{
  708. {
  709. name: "without quote",
  710. args: args{
  711. value: "Hello\nWorld",
  712. },
  713. want: "```",
  714. },
  715. {
  716. name: "with 1 quote",
  717. args: args{
  718. value: "Hello\nWorld\n`text`\n",
  719. },
  720. want: "```",
  721. },
  722. {
  723. name: "with 3 quotes",
  724. args: args{
  725. value: "Hello\nWorld\n`text`\n```go\ntext\n```\n",
  726. },
  727. want: "````",
  728. },
  729. {
  730. name: "with more quotes",
  731. args: args{
  732. value: "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n",
  733. },
  734. want: "```````````",
  735. },
  736. {
  737. name: "not leading quotes",
  738. args: args{
  739. value: "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n",
  740. },
  741. want: "```",
  742. },
  743. }
  744. for _, tt := range tests {
  745. t.Run(tt.name, func(t *testing.T) {
  746. if got := minQuotes(tt.args.value); got != tt.want {
  747. t.Errorf("minQuotes() = %v, want %v", got, tt.want)
  748. }
  749. })
  750. }
  751. }