github.com/tomwright/dasel@v1.27.3/internal/command/root_select_test.go (about)

     1  package command_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/tomwright/dasel/internal/command"
     7  	"io"
     8  	"strings"
     9  	"testing"
    10  )
    11  
    12  const jsonDataSingle = `{"x": "asd"}`
    13  const yamlDataSingle = `x: asd`
    14  const tomlDataSingle = `x="asd"`
    15  const xmlDataSingle = `<x>asd</x>`
    16  
    17  const jsonData = `{
    18    "id": "1111",
    19    "details": {
    20      "name": "Tom",
    21    	"age": 27,
    22      "addresses": [
    23        {
    24          "street": "101 Some Street",
    25          "town": "Some Town",
    26          "county": "Some Country",
    27          "postcode": "XXX XXX",
    28          "primary": true
    29        },
    30        {
    31          "street": "34 Another Street",
    32          "town": "Another Town",
    33          "county": "Another County",
    34          "postcode": "YYY YYY"
    35        }
    36      ]
    37    }
    38  }`
    39  
    40  const yamlData = `
    41  id: 1111
    42  details:
    43    name: Tom
    44    age: 27
    45    addresses:
    46    - street: 101 Some Street
    47      town: Some Town
    48      county: Some County
    49      postcode: XXX XXX
    50      primary: true
    51    - street: 34 Another Street
    52      town: Another Town
    53      county: Another County
    54      postcode: YYY YYY
    55  `
    56  
    57  const tomlData = `id = "1111"
    58  [details]
    59    name = "Tom"
    60    age = 27
    61    [[details.addresses]]
    62      street =  "101 Some Street"
    63      town = "Some Town"
    64      county = "Some County"
    65      postcode = "XXX XXX"
    66      primary = true
    67    [[details.addresses]]
    68      street = "34 Another Street"
    69      town = "Another Town"
    70      county = "Another County"
    71      postcode = "YYY YYY"
    72  `
    73  
    74  const xmlData = `<data>
    75  	<id>1111</id>
    76  	<details>
    77  		<name>Tom</name>
    78  		<age>27</age>
    79  		<addresses primary="true">
    80  			<street>101 Some Street</street>
    81  			<town>Some Town</town>
    82  			<county>Some County</county>
    83  			<postcode>XXX XXX</postcode>
    84  		</addresses>
    85  		<addresses>
    86  			<street>34 Another Street</street>
    87  			<town>Another Town</town>
    88  			<county>Another County</county>
    89  			<postcode>YYY YYY</postcode>
    90  		</addresses>
    91  	</details>
    92  </data>
    93  `
    94  
    95  const csvData = `id,name
    96  1,Tom
    97  2,Jim
    98  `
    99  
   100  func newline(x string) string {
   101  	return x + "\n"
   102  }
   103  
   104  func TestRootCMD_Select(t *testing.T) {
   105  	t.Run("InvalidFile", expectErr(
   106  		[]string{"select", "-f", "bad.json", "-s", "x"},
   107  		"could not open input file",
   108  	))
   109  	t.Run("MissingParser", expectErr(
   110  		[]string{"select", "-s", "x"},
   111  		"parser flag required when reading from stdin",
   112  	))
   113  	t.Run("Stdin", expectOutput(
   114  		`{"name": "Tom"}`,
   115  		[]string{"select", "-f", "stdin", "-p", "json", "-s", ".name"},
   116  		`"Tom"
   117  `,
   118  	))
   119  	t.Run("StdinAlias", expectOutput(
   120  		`{"name": "Tom"}`,
   121  		[]string{"select", "-f", "-", "-p", "json", "-s", ".name"},
   122  		`"Tom"
   123  `,
   124  	))
   125  
   126  	t.Run("InvalidSingleSelector", expectErrFromInput(
   127  		`{"name": "Tom"}`,
   128  		[]string{"select", "-p", "json", "-s", "[-]"},
   129  		"invalid index: -",
   130  	))
   131  	t.Run("InvalidMultiSelector", expectErrFromInput(
   132  		`{"name": "Tom"}`,
   133  		[]string{"select", "-p", "json", "-m", "-s", "[-]"},
   134  		"invalid index: -",
   135  	))
   136  }
   137  
   138  func selectTest(in string, parser string, selector string, output string, expErr error, additionalArgs ...string) func(t *testing.T) {
   139  	return selectTestCheck(in, parser, selector, func(out string) error {
   140  		if out != output {
   141  			return fmt.Errorf("expected %v, got %v", output, out)
   142  		}
   143  		return nil
   144  	}, expErr, additionalArgs...)
   145  }
   146  
   147  func selectTestContainsLines(in string, parser string, selector string, output []string, expErr error, additionalArgs ...string) func(t *testing.T) {
   148  	return selectTestCheck(in, parser, selector, func(out string) error {
   149  		splitOut := strings.Split(out, "\n")
   150  		for _, s := range output {
   151  			found := false
   152  			for _, got := range splitOut {
   153  				if s == got {
   154  					found = true
   155  					break
   156  				}
   157  			}
   158  			if !found {
   159  				return fmt.Errorf("required value not found: %s", s)
   160  			}
   161  		}
   162  		return nil
   163  	}, expErr, additionalArgs...)
   164  }
   165  
   166  func selectTestCheck(in string, parser string, selector string, checkFn func(out string) error, expErr error, additionalArgs ...string) func(t *testing.T) {
   167  	return func(t *testing.T) {
   168  		cmd := command.NewRootCMD()
   169  		outputBuffer := bytes.NewBuffer([]byte{})
   170  
   171  		args := []string{
   172  			"select", "-p", parser,
   173  		}
   174  		if additionalArgs != nil {
   175  			args = append(args, additionalArgs...)
   176  		}
   177  		args = append(args, selector)
   178  
   179  		cmd.SetOut(outputBuffer)
   180  		cmd.SetIn(strings.NewReader(in))
   181  		cmd.SetArgs(args)
   182  
   183  		err := cmd.Execute()
   184  
   185  		if expErr == nil && err != nil {
   186  			t.Errorf("expected err %v, got %v", expErr, err)
   187  			return
   188  		}
   189  		if expErr != nil && err == nil {
   190  			t.Errorf("expected err %v, got %v", expErr, err)
   191  			return
   192  		}
   193  		if expErr != nil && err != nil && err.Error() != expErr.Error() {
   194  			t.Errorf("expected err %v, got %v", expErr, err)
   195  			return
   196  		}
   197  
   198  		output, err := io.ReadAll(outputBuffer)
   199  		if err != nil {
   200  			t.Errorf("unexpected error reading output buffer: %s", err)
   201  			return
   202  		}
   203  
   204  		if err := checkFn(string(output)); err != nil {
   205  			t.Errorf("unexpected output: %s", err)
   206  		}
   207  	}
   208  }
   209  
   210  func selectTestFromFile(inputPath string, selector string, out string, expErr error) func(t *testing.T) {
   211  	return func(t *testing.T) {
   212  		cmd := command.NewRootCMD()
   213  		outputBuffer := bytes.NewBuffer([]byte{})
   214  
   215  		args := []string{
   216  			"select", "-f", inputPath, "-s", selector,
   217  		}
   218  
   219  		cmd.SetOut(outputBuffer)
   220  		cmd.SetArgs(args)
   221  
   222  		err := cmd.Execute()
   223  
   224  		if expErr == nil && err != nil {
   225  			t.Errorf("expected err %v, got %v", expErr, err)
   226  			return
   227  		}
   228  		if expErr != nil && err == nil {
   229  			t.Errorf("expected err %v, got %v", expErr, err)
   230  			return
   231  		}
   232  		if expErr != nil && err != nil && err.Error() != expErr.Error() {
   233  			t.Errorf("expected err %v, got %v", expErr, err)
   234  			return
   235  		}
   236  
   237  		output, err := io.ReadAll(outputBuffer)
   238  		if err != nil {
   239  			t.Errorf("unexpected error reading output buffer: %s", err)
   240  			return
   241  		}
   242  
   243  		if out != string(output) {
   244  			t.Errorf("expected result %v, got %v", out, string(output))
   245  		}
   246  	}
   247  }
   248  
   249  func TestRootCmd_Select_JSON(t *testing.T) {
   250  	t.Run("RootElement", selectTest(jsonDataSingle, "json", ".", newline(`{
   251    "x": "asd"
   252  }`), nil))
   253  	t.Run("SingleProperty", selectTest(jsonData, "json", ".id", newline(`"1111"`), nil))
   254  	t.Run("ObjectProperty", selectTest(jsonData, "json", ".details.name", newline(`"Tom"`), nil))
   255  	t.Run("Index", selectTest(jsonData, "json", ".details.addresses.[0].street", newline(`"101 Some Street"`), nil))
   256  	t.Run("Index", selectTest(jsonData, "json", ".details.addresses.[1].street", newline(`"34 Another Street"`), nil))
   257  	t.Run("DynamicString", selectTest(jsonData, "json", ".details.addresses.(postcode=XXX XXX).street", newline(`"101 Some Street"`), nil))
   258  	t.Run("DynamicString", selectTest(jsonData, "json", ".details.addresses.(postcode=YYY YYY).street", newline(`"34 Another Street"`), nil))
   259  	t.Run("QueryFromFile", selectTestFromFile("./../../tests/assets/example.json", ".preferences.favouriteColour", newline(`"red"`), nil))
   260  
   261  	t.Run("MultiProperty", selectTest(jsonData, "json", ".details.addresses.[*].street", newline(`"101 Some Street"
   262  "34 Another Street"`), nil, "-m"))
   263  
   264  	t.Run("MultiRoot", selectTest(jsonDataSingle, "json", ".", newline(`{
   265    "x": "asd"
   266  }`), nil, "-m"))
   267  
   268  	t.Run("SubSelector", selectTest(`{
   269    "users": [
   270  	{
   271  	  "primary": true,
   272  	  "name": {
   273  		"first": "Tom",
   274  		"last": "Wright"
   275  	  }
   276  	},
   277  	{
   278  	  "primary": false,
   279  	  "name": {
   280  		"first": "Jim",
   281  		"last": "Wright"
   282  	  }
   283  	}
   284    ]
   285  }`, "json", ".users.(name.first=Tom).primary", newline(`true`), nil))
   286  
   287  	t.Run("SubSubSelector", selectTest(`{
   288    "users": [
   289  	{
   290  	  "name": {
   291  		"first": "Tom",
   292  		"last": "Wright"
   293  	  },
   294        "addresses": [
   295          {
   296            "primary": true,
   297            "number": 123
   298          },
   299          {
   300            "primary": false,
   301            "number": 456
   302          }
   303        ]
   304  	}
   305    ]
   306  }`, "json", ".users.(.addresses.(.primary=true).number=123).name.first", newline(`"Tom"`), nil))
   307  
   308  	t.Run("SubSubAndSelector", selectTest(`{
   309    "users": [
   310  	{
   311  	  "name": {
   312  		"first": "Tom",
   313  		"last": "Wright"
   314  	  },
   315        "addresses": [
   316          {
   317            "primary": true,
   318            "number": 123
   319          },
   320          {
   321            "primary": false,
   322            "number": 456
   323          }
   324        ]
   325  	}
   326    ]
   327  }`, "json", ".users.(.addresses.(.primary=true).number=123)(.name.last=Wright).name.first", newline(`"Tom"`), nil))
   328  
   329  	t.Run("KeySearch", selectTestContainsLines(`{
   330    "users": [
   331      {
   332        "primary": true,
   333        "name": {
   334          "first": "Tom",
   335          "last": "Wright"
   336        }
   337      },
   338      {
   339        "primary": false,
   340        "extra": {
   341          "name": {
   342            "first": "Joe",
   343            "last": "Blogs"
   344          }
   345        },
   346        "name": {
   347          "first": "Jim",
   348          "last": "Wright"
   349        }
   350      }
   351    ]
   352  }`, "json", ".(?:-=name).first", []string{`"Tom"`, `"Joe"`, `"Jim"`}, nil, "-m"))
   353  
   354  	t.Run("NullNotFound", selectTest(`{}`, "json", ".asd", newline(`null`), nil, "-n"))
   355  	t.Run("NullNotFoundMulti", selectTest(`{}`, "json", ".asd", newline(`null`), nil, "-m", "-n"))
   356  
   357  	t.Run("ObjectKeysSelector", selectTestContainsLines(jsonData, "json", ".-", []string{`"id"`,
   358  		`"details"`}, nil, "-m"))
   359  
   360  	t.Run("ArrayIndexesSelector", selectTest(jsonData, "json", ".details.addresses.-", newline(`"0"
   361  "1"`), nil, "-m"))
   362  
   363  	t.Run("RootElementCompactShortFlag", selectTest(`{
   364    "x": "asd"
   365  }`, "json", ".", newline(`{"x":"asd"}`), nil, "-c"))
   366  	t.Run("RootElementCompactLongFlag", selectTest(`{
   367    "x": "asd"
   368  }`, "json", ".", newline(`{"x":"asd"}`), nil, "--compact"))
   369  
   370  	t.Run("LengthFlagList", selectTest(`{
   371    "x": [ "a", "b", "c" ]
   372  }`, "json", ".x", newline(`3`), nil, "--length"))
   373  	t.Run("LengthFlagMap", selectTest(`{
   374    "x": { "a": 1, "b": 2, "c": 3 }
   375  }`, "json", ".x", newline(`3`), nil, "--length"))
   376  	t.Run("LengthFlagString", selectTest(`{
   377    "x": "asd"
   378  }`, "json", ".x", newline(`3`), nil, "--length"))
   379  	t.Run("LengthFlagInt", selectTest(`{
   380    "x": 123
   381  }`, "json", ".x", newline(`3`), nil, "--length"))
   382  	t.Run("LengthFlagBool", selectTest(`{
   383    "x": true
   384  }`, "json", ".x", newline(`4`), nil, "--length"))
   385  	t.Run("LengthFlagMulti", selectTest(`[
   386    [ "a", "b", "c" ],
   387    { "a": 1 },
   388    "hello there",
   389    12345,
   390    123.45,
   391    true
   392  ]`, "json", ".[*]", newline(`3
   393  1
   394  11
   395  5
   396  6
   397  4`), nil, "--length", "-m"))
   398  
   399  	t.Run("NullInput", selectTest(`null`, "json", `.`, newline("{}"), nil))
   400  	t.Run("EmptyDocument", selectTest(`{}`, "json", `.`, newline("{}"), nil))
   401  	t.Run("EmptyArray", selectTest(`[]`, "json", `.`, newline("[]"), nil))
   402  	t.Run("BlankInput", selectTest(``, "json", `.`, newline("{}"), nil))
   403  
   404  	t.Run("LengthSelector", selectTest(jsonData, "json", `.details.addresses.[#]`, newline("2"), nil))
   405  	t.Run("LengthSelectorMultiple", selectTest(jsonData, "json", `.details.addresses.[#]`, newline("2"), nil, "-m"))
   406  	t.Run("LengthSelectorMultiple", selectTest(jsonData, "json", `.details.addresses.[*].[#]`, newline("5\n4"), nil, "-m"))
   407  	t.Run("LengthDynamicSelector", selectTest(`{
   408    "a": {
   409      "id": 1,
   410      "uses": [1]
   411    },
   412    "b": {
   413      "id": 2,
   414      "uses": [1, 2]
   415    },
   416    "c": {
   417      "id": 3,
   418      "uses": [1, 2, 3]
   419    }
   420  }`, "json", `.(.uses.[#]=2).id`, newline("2"), nil))
   421  	t.Run("LengthDynamicSelectorMultiple", selectTest(`[
   422    {
   423      "id": 1,
   424      "uses": [1]
   425    },
   426    {
   427      "id": 2,
   428      "uses": [1, 2]
   429    },
   430    {
   431      "id": 3,
   432      "uses": [1, 2, 3]
   433    },
   434    {
   435      "id": 4,
   436      "uses": [3, 4]
   437    }
   438  ]`, "json", `.(.uses.[#]=2).id`, newline("2\n4"), nil, "-m"))
   439  	t.Run("LengthDynamicSelectorMoreThan", selectTest(`[
   440    {
   441      "id": 1,
   442      "uses": [1]
   443    },
   444    {
   445      "id": 2,
   446      "uses": [1, 2]
   447    },
   448    {
   449      "id": 3,
   450      "uses": [1, 2, 3]
   451    },
   452    {
   453      "id": 4,
   454      "uses": [3, 4]
   455    }
   456  ]`, "json", `.(.uses.[#]>2).id`, newline("3"), nil, "-m"))
   457  
   458  	t.Run("MergeInputDocuments", selectTest(`{
   459    "number": 1
   460  }
   461  {
   462    "number": 2
   463  }
   464  {
   465    "number": 3
   466  }
   467  `, "json", `.`, `[
   468    {
   469      "number": 1
   470    },
   471    {
   472      "number": 2
   473    },
   474    {
   475      "number": 3
   476    }
   477  ]
   478  `, nil, "--merge-input-documents"))
   479  
   480  	t.Run("EscapeHTMLOn", selectTest(`{
   481    "user": "Tom <contact@tomwright.me>"
   482  }
   483  `, "json", `.`, `{
   484    "user": "Tom \u003ccontact@tomwright.me\u003e"
   485  }
   486  `, nil, "--escape-html=true"))
   487  
   488  	t.Run("EscapeHTMLOff", selectTest(`{
   489    "user": "Tom <contact@tomwright.me>"
   490  }
   491  `, "json", `.`, `{
   492    "user": "Tom <contact@tomwright.me>"
   493  }
   494  `, nil, "--escape-html=false"))
   495  
   496  	t.Run("MixedDynamicSelectors", selectTest(`{
   497    "plugins": [
   498      "@semantic-release/commit-analyzer",
   499      "@semantic-release/release-notes-generator",
   500      "@semantic-release/gitlab",
   501      [
   502        "@semantic-release/git",
   503        {
   504          "assets": [
   505            "tbump.toml",
   506            "**/pyproject.toml",
   507            "**/setup.py",
   508            "README.md"
   509          ],
   510          "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
   511        }
   512      ],
   513  	[
   514        "@semantic-release/git",
   515        {
   516          "assets": [
   517            "y"
   518          ],
   519          "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
   520        }
   521      ]
   522    ]
   523  }`, "json", `.plugins.([@]=array).([@]=map).assets`, `[
   524    "tbump.toml",
   525    "**/pyproject.toml",
   526    "**/setup.py",
   527    "README.md"
   528  ]
   529  [
   530    "y"
   531  ]
   532  `, nil, "-m"))
   533  
   534  	t.Run("SearchOptional", selectTest(`{
   535    "users": [
   536      {
   537        "name": "Tom",
   538        "blocked": true
   539      },
   540      {
   541        "name": "Jim",
   542        "blocked": false
   543      },
   544      {
   545        "name": "Frank"
   546      }
   547    ]
   548  }`, "json", `.users.(#:blocked=true).name`, `Tom
   549  `, nil, "-m", "--plain"))
   550  
   551  }
   552  
   553  func TestRootCmd_Select_YAML(t *testing.T) {
   554  	t.Run("RootElement", selectTest(yamlDataSingle, "yaml", ".", newline(`x: asd`), nil))
   555  	t.Run("SingleProperty", selectTest(yamlData, "yaml", ".id", newline(`1111`), nil))
   556  	t.Run("ObjectProperty", selectTest(yamlData, "yaml", ".details.name", newline(`Tom`), nil))
   557  	t.Run("Index", selectTest(yamlData, "yaml", ".details.addresses.[0].street", newline(`101 Some Street`), nil))
   558  	t.Run("Index", selectTest(yamlData, "yaml", ".details.addresses.[1].street", newline(`34 Another Street`), nil))
   559  	t.Run("DynamicString", selectTest(yamlData, "yaml", ".details.addresses.(postcode=XXX XXX).street", newline(`101 Some Street`), nil))
   560  	t.Run("DynamicString", selectTest(yamlData, "yaml", ".details.addresses.(postcode=YYY YYY).street", newline(`34 Another Street`), nil))
   561  	t.Run("QueryFromFile", selectTestFromFile("./../../tests/assets/example.yaml", ".preferences.favouriteColour", newline(`red`), nil))
   562  
   563  	// Following test implemented as a result of issue #35.
   564  	t.Run("MultipleSeparateDynamic", selectTest(`
   565  apiVersion: apps/v1
   566  kind: Deployment
   567  metadata:
   568    name: harbor-exporter
   569    labels:
   570      app: harbor-exporter
   571  spec:
   572    replicas: 1
   573    selector:
   574      matchLabels:
   575        app: harbor-exporter
   576    strategy:
   577      type: RollingUpdate
   578    template:
   579      metadata:
   580        labels:
   581          app: harbor-exporter
   582      spec:
   583        serviceAccountName: default
   584        restartPolicy: Always
   585        securityContext:
   586          runAsNonRoot: true
   587          runAsUser: 1000
   588        containers:
   589          - name: harbor-exporter
   590            image: "c4po/harbor-exporter:debug"
   591            imagePullPolicy: Always
   592            env:
   593              - name: HARBOR_URI
   594  #            name of the Service for harbor-core
   595                value: http://harbor-core.harbor # change prefix to the name of your Helm release
   596              - name: HARBOR_USERNAME
   597                value: "admin"
   598              - name: HARBOR_PASSWORD
   599                valueFrom:
   600                  secretKeyRef:
   601                    name: harbor-core # change prefix to the name of your Helm release
   602                    key: HARBOR_ADMIN_PASSWORD
   603  
   604            securityContext:
   605              capabilities:
   606                drop:
   607                  - SETPCAP
   608                  - MKNOD
   609                  - AUDIT_WRITE
   610                  - CHOWN
   611                  - NET_RAW
   612                  - DAC_OVERRIDE
   613                  - FOWNER
   614                  - FSETID
   615                  - KILL
   616                  - SETGID
   617                  - SETUID
   618                  - NET_BIND_SERVICE
   619                  - SYS_CHROOT
   620                  - SETFCAP
   621              readOnlyRootFilesystem: true
   622            resources:
   623              limits:
   624                cpu: 400m
   625                memory: 256Mi
   626              requests:
   627                cpu: 100m
   628                memory: 64Mi
   629            ports:
   630              - containerPort: 9107
   631                name: http
   632            livenessProbe:
   633              httpGet:
   634                path: /-/healthy
   635                port: http
   636              initialDelaySeconds: 5
   637              timeoutSeconds: 5
   638              periodSeconds: 5
   639            readinessProbe:
   640              httpGet:
   641                path: /-/ready
   642                port: http
   643              initialDelaySeconds: 1
   644              timeoutSeconds: 5
   645              periodSeconds: 5
   646  `, "yaml", "spec.template.spec.containers.(name=harbor-exporter).env.(name=HARBOR_URI).value", newline(`http://harbor-core.harbor`), nil))
   647  
   648  	// https://github.com/TomWright/dasel/issues/99
   649  	// Worked in v1.13.3
   650  	t.Run("NullInput", selectTest(`null`, "yaml", `.`, newline("{}"), nil))
   651  	t.Run("EmptyDocument", selectTest(`---`, "yaml", `.`, newline("{}"), nil))
   652  	t.Run("BlankInput", selectTest(``, "yaml", `.`, newline("{}"), nil))
   653  
   654  	// https://github.com/TomWright/dasel/discussions/244
   655  	t.Run("DynamicSelectorContainingEqualsInValue", selectTest(`
   656  options:
   657    k3s:
   658      extraArgs:
   659        - arg: --no-deploy=traefik
   660          nodeFilters:
   661            - server:*
   662        - arg: --kube-apiserver-arg=feature-gates=HPAContainerMetrics=true
   663          nodeFilters:
   664            - server:*
   665  `, "yaml", `.options.k3s.extraArgs.(arg=--no-deploy=traefik)`, newline(`arg: --no-deploy=traefik
   666  nodeFilters:
   667  - server:*`), nil))
   668  }
   669  
   670  func TestRootCmd_Select_TOML(t *testing.T) {
   671  	t.Run("RootElement", selectTest(tomlDataSingle, "toml", ".", newline(`x = "asd"`), nil))
   672  	t.Run("SingleProperty", selectTest(tomlData, "toml", ".id", newline(`1111`), nil))
   673  	t.Run("ObjectProperty", selectTest(tomlData, "toml", ".details.name", newline(`Tom`), nil))
   674  	t.Run("Index", selectTest(tomlData, "toml", ".details.addresses.[0].street", newline(`101 Some Street`), nil))
   675  	t.Run("Index", selectTest(tomlData, "toml", ".details.addresses.[1].street", newline(`34 Another Street`), nil))
   676  	t.Run("DynamicString", selectTest(tomlData, "toml", ".details.addresses.(postcode=XXX XXX).street", newline(`101 Some Street`), nil))
   677  	t.Run("DynamicString", selectTest(tomlData, "toml", ".details.addresses.(postcode=YYY YYY).street", newline(`34 Another Street`), nil))
   678  }
   679  
   680  func TestRootCMD_Select_XML(t *testing.T) {
   681  	t.Run("RootElement", selectTest(xmlDataSingle, "xml", ".", newline(`<x>asd</x>`), nil))
   682  	t.Run("SingleProperty", selectTest(xmlData, "xml", ".data.id", "1111\n", nil))
   683  	t.Run("ObjectProperty", selectTest(xmlData, "xml", ".data.details.name", "Tom\n", nil))
   684  	t.Run("Index", selectTest(xmlData, "xml", ".data.details.addresses.[0].street", "101 Some Street\n", nil))
   685  	t.Run("Index", selectTest(xmlData, "xml", ".data.details.addresses.[1].street", "34 Another Street\n", nil))
   686  	t.Run("DynamicString", selectTest(xmlData, "xml", ".data.details.addresses.(postcode=XXX XXX).street", "101 Some Street\n", nil))
   687  	t.Run("DynamicString", selectTest(xmlData, "xml", ".data.details.addresses.(postcode=YYY YYY).street", "34 Another Street\n", nil))
   688  	t.Run("Attribute", selectTest(xmlData, "xml", ".data.details.addresses.(-primary=true).street", "101 Some Street\n", nil))
   689  
   690  	t.Run("KeySearch", selectTestContainsLines(`
   691  <food>
   692    <tart>
   693      <apple color="yellow"/>
   694    </tart>
   695    <pie>
   696      <crust quality="flaky"/>
   697      <filling>
   698        <apple color="red"/>
   699      </filling>
   700    </pie>
   701    <apple color="green"/>
   702  </food>
   703  `, "xml", ".food.(?:keyValue=apple).-color", []string{"yellow", "red", "green"}, nil, "-m"))
   704  }
   705  
   706  func TestRootCMD_Select_CSV(t *testing.T) {
   707  	t.Run("RootElement", selectTest(csvData, "csv", ".", csvData, nil))
   708  	t.Run("SingleProperty", selectTest(csvData, "csv", ".[0].id", "1\n", nil))
   709  	t.Run("SingleProperty", selectTest(csvData, "csv", ".[1].id", "2\n", nil))
   710  
   711  	// https://github.com/TomWright/dasel/issues/110
   712  	t.Run("ObjectArrayJSONToCSV", selectTest(`
   713  [
   714    {
   715      "id": "ABS",
   716      "name": "Australian Bureau of Statistics"
   717    },
   718    {
   719      "id": "ECB",
   720      "name": "European Central Bank"
   721    },
   722    {
   723      "id": "ESTAT",
   724      "name": "Eurostat"
   725    },
   726    {
   727      "id": "ILO",
   728      "name": "International Labor Organization"
   729    }
   730  ]
   731  `, "json", ".", `id,name
   732  ABS,Australian Bureau of Statistics
   733  ECB,European Central Bank
   734  ESTAT,Eurostat
   735  ILO,International Labor Organization
   736  `, nil, "-w", "csv"))
   737  
   738  	// https://github.com/TomWright/dasel/issues/159
   739  	t.Run("SelectFilterOnUnicode", selectTest(`
   740  {
   741     "data": [
   742       {"name": "Fu Shun", "ship_type": "Destroyer"},
   743       {"name": "Sheffield", "ship_type": "Light Cruiser"},
   744       {"name": "Ägir", "ship_type": "Large Cruiser"}
   745     ]
   746   }
   747  `, "json", ".data.(name=Ägir).ship_type", `"Large Cruiser"
   748  `, nil))
   749  }
   750  
   751  func TestRootCmd_Select_JSON_Format(t *testing.T) {
   752  	t.Run("RootElementFormattedToProperty", selectTest(jsonData, "json", ".", newline(`1111`), nil,
   753  		"--format", `{{ query ".id" }}`))
   754  	t.Run("SelectorFormatted", selectTest(jsonData, "json", ".id", newline(`1111`), nil,
   755  		"--format", `{{ . }}`))
   756  	t.Run("SelectorFormattedMultiple", selectTest(jsonData, "json", ".details.addresses.[*]",
   757  		newline(`101 Some Street
   758  34 Another Street`), nil,
   759  		"-m", "--format", `{{ query ".street" }}`))
   760  	t.Run("SelectorFormattedToMultiple", selectTest(jsonData, "json", ".",
   761  		newline(`101 Some Street
   762  34 Another Street`), nil,
   763  		"-m", "--format", `{{ queryMultiple ".details.addresses.[*]" | format "{{ .street }}{{ if not isLast }}{{ newline }}{{end}}" }}`))
   764  
   765  	// https://github.com/TomWright/dasel/discussions/146
   766  	t.Run("Discussion146", selectTest(
   767  		`[{"name": "click", "version": "7.1.2", "latest_version": "8.0.1", "latest_filetype": "wheel"}, {"name": "decorator", "version": "4.4.2", "latest_version": "5.0.9", "latest_filetype": "wheel"}, {"name": "ipython", "version": "7.20.0", "latest_version": "7.25.0", "latest_filetype": "wheel"}, {"name": "pandas", "version": "1.3.0", "latest_version": "1.3.1", "latest_filetype": "wheel"}, {"name": "parso", "version": "0.8.1", "latest_version": "0.8.2", "latest_filetype": "wheel"}, {"name": "pip", "version": "21.1.3", "latest_version": "21.2.1", "latest_filetype": "wheel"}, {"name": "prompt-toolkit", "version": "3.0.14", "latest_version": "3.0.19", "latest_filetype": "wheel"}, {"name": "Pygments", "version": "2.7.4", "latest_version": "2.9.0", "latest_filetype": "wheel"}, {"name": "setuptools", "version": "49.2.1", "latest_version": "57.4.0", "latest_filetype": "wheel"}, {"name": "tomli", "version": "1.0.4", "latest_version": "1.1.0", "latest_filetype": "wheel"}]`,
   768  		"json", ".(name!=setuptools)(name!=six)(name!=pip)(name!=pip-tools)",
   769  		newline(`click
   770  7.1.2
   771  8.0.1
   772  decorator
   773  4.4.2
   774  5.0.9
   775  ipython
   776  7.20.0
   777  7.25.0
   778  pandas
   779  1.3.0
   780  1.3.1
   781  parso
   782  0.8.1
   783  0.8.2
   784  prompt-toolkit
   785  3.0.14
   786  3.0.19
   787  Pygments
   788  2.7.4
   789  2.9.0
   790  tomli
   791  1.0.4
   792  1.1.0`), nil,
   793  		"-m", "--format", `{{ query ".name" }}{{ newline }}{{ query ".version" }}{{ newline }}{{ query ".latest_version" }}`))
   794  }