2020-08-30 03:30:07 +00:00
|
|
|
package configfilearg
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
2021-07-13 17:44:11 +00:00
|
|
|
"reflect"
|
2020-08-30 03:30:07 +00:00
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2021-07-26 16:59:33 +00:00
|
|
|
func Test_UnitParser_findStart(t *testing.T) {
|
2021-07-13 17:44:11 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args []string
|
2020-08-30 03:30:07 +00:00
|
|
|
prefix []string
|
|
|
|
suffix []string
|
|
|
|
found bool
|
|
|
|
}{
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "default case",
|
|
|
|
args: nil,
|
2020-08-30 03:30:07 +00:00
|
|
|
prefix: nil,
|
|
|
|
suffix: nil,
|
|
|
|
found: false,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "simple case",
|
|
|
|
args: []string{"server"},
|
2020-08-30 03:30:07 +00:00
|
|
|
prefix: []string{"server"},
|
|
|
|
suffix: []string{},
|
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "also simple case",
|
|
|
|
args: []string{"server", "foo"},
|
2020-08-30 03:30:07 +00:00
|
|
|
prefix: []string{"server"},
|
|
|
|
suffix: []string{"foo"},
|
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "longer simple case",
|
|
|
|
args: []string{"server", "foo", "bar"},
|
2020-08-30 03:30:07 +00:00
|
|
|
prefix: []string{"server"},
|
|
|
|
suffix: []string{"foo", "bar"},
|
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "not found",
|
|
|
|
args: []string{"not-server", "foo", "bar"},
|
2020-08-30 03:30:07 +00:00
|
|
|
prefix: []string{"not-server", "foo", "bar"},
|
|
|
|
found: false,
|
|
|
|
},
|
2021-11-08 18:54:48 +00:00
|
|
|
{
|
|
|
|
name: "command (with optional subcommands) but no flags",
|
|
|
|
args: []string{"etcd-snapshot"},
|
|
|
|
prefix: []string{"etcd-snapshot"},
|
|
|
|
suffix: []string{},
|
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "command (with optional subcommands) and flags",
|
|
|
|
args: []string{"etcd-snapshot", "-f"},
|
|
|
|
prefix: []string{"etcd-snapshot"},
|
|
|
|
suffix: []string{"-f"},
|
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "command and subcommand with no flags",
|
|
|
|
args: []string{"etcd-snapshot", "list"},
|
|
|
|
prefix: []string{"etcd-snapshot", "list"},
|
|
|
|
suffix: []string{},
|
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "command and subcommand with flags",
|
|
|
|
args: []string{"etcd-snapshot", "list", "-f"},
|
|
|
|
prefix: []string{"etcd-snapshot", "list"},
|
|
|
|
suffix: []string{"-f"},
|
|
|
|
found: true,
|
|
|
|
},
|
2021-11-12 00:01:23 +00:00
|
|
|
{
|
|
|
|
name: "another command and subcommand with flags",
|
|
|
|
args: []string{"etcd-snapshot", "save", "--s3"},
|
|
|
|
prefix: []string{"etcd-snapshot", "save"},
|
|
|
|
suffix: []string{"--s3"},
|
|
|
|
found: true,
|
|
|
|
},
|
2021-11-08 18:54:48 +00:00
|
|
|
{
|
|
|
|
name: "command and too many subcommands",
|
|
|
|
args: []string{"etcd-snapshot", "list", "delete", "foo", "bar"},
|
|
|
|
prefix: []string{"etcd-snapshot", "list"},
|
|
|
|
suffix: []string{"delete", "foo", "bar"},
|
|
|
|
found: true,
|
|
|
|
},
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
2021-07-13 17:44:11 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
p := Parser{
|
2021-11-08 18:54:48 +00:00
|
|
|
After: []string{"server", "agent", "etcd-snapshot:1"},
|
2021-07-13 17:44:11 +00:00
|
|
|
}
|
|
|
|
prefix, suffix, found := p.findStart(tt.args)
|
|
|
|
if !reflect.DeepEqual(prefix, tt.prefix) {
|
|
|
|
t.Errorf("Parser.findStart() prefix = %+v\nWant = %+v", prefix, tt.prefix)
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(suffix, tt.suffix) {
|
|
|
|
t.Errorf("Parser.findStart() suffix = %+v\nWant = %+v", suffix, tt.suffix)
|
|
|
|
}
|
|
|
|
if found != tt.found {
|
|
|
|
t.Errorf("Parser.findStart() found = %+v\nWant = %+v", found, tt.found)
|
|
|
|
}
|
|
|
|
})
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 16:59:33 +00:00
|
|
|
func Test_UnitParser_findConfigFileFlag(t *testing.T) {
|
2021-07-13 17:44:11 +00:00
|
|
|
type fields struct {
|
|
|
|
DefaultConfig string
|
|
|
|
env string
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
arg []string
|
|
|
|
want string
|
|
|
|
found bool
|
2020-08-30 03:30:07 +00:00
|
|
|
}{
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "default case",
|
|
|
|
arg: nil,
|
2020-08-30 03:30:07 +00:00
|
|
|
found: false,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "simple case",
|
|
|
|
arg: []string{"asdf", "-c", "value"},
|
|
|
|
want: "value",
|
|
|
|
found: true,
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "invalid args string",
|
|
|
|
arg: []string{"-c"},
|
2020-08-30 03:30:07 +00:00
|
|
|
found: false,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "empty arg value",
|
|
|
|
arg: []string{"-c="},
|
2020-08-30 03:30:07 +00:00
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "empty arg value override default",
|
|
|
|
fields: fields{
|
|
|
|
DefaultConfig: "def",
|
|
|
|
},
|
|
|
|
arg: []string{"-c="},
|
2020-08-30 03:30:07 +00:00
|
|
|
found: true,
|
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
fields: fields{
|
|
|
|
DefaultConfig: "def",
|
|
|
|
},
|
|
|
|
arg: []string{"-c"},
|
2020-08-30 03:30:07 +00:00
|
|
|
found: false,
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "invalid args always return no value",
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
fields: fields{
|
|
|
|
DefaultConfig: "def",
|
|
|
|
},
|
|
|
|
arg: []string{"-c", "value"},
|
|
|
|
want: "value",
|
|
|
|
found: true,
|
|
|
|
name: "value override default",
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
fields: fields{
|
|
|
|
DefaultConfig: "def",
|
|
|
|
},
|
|
|
|
want: "def",
|
|
|
|
found: false,
|
|
|
|
name: "default gets used when nothing is passed",
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "env override args",
|
|
|
|
fields: fields{
|
|
|
|
DefaultConfig: "def",
|
|
|
|
env: "env",
|
|
|
|
},
|
|
|
|
arg: []string{"-c", "value"},
|
|
|
|
want: "env",
|
|
|
|
found: true,
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "garbage in start and end",
|
|
|
|
fields: fields{
|
|
|
|
DefaultConfig: "def",
|
|
|
|
},
|
|
|
|
arg: []string{"before", "-c", "value", "after"},
|
|
|
|
want: "value",
|
|
|
|
found: true,
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
}
|
2021-07-13 17:44:11 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
p := Parser{
|
2023-06-07 22:57:52 +00:00
|
|
|
ConfigFlags: []string{"--config", "-c"},
|
2021-07-13 17:44:11 +00:00
|
|
|
EnvName: "_TEST_FLAG_ENV",
|
|
|
|
DefaultConfig: tt.fields.DefaultConfig,
|
|
|
|
}
|
|
|
|
// Setup
|
|
|
|
defer os.Unsetenv(tt.fields.env)
|
|
|
|
os.Setenv(p.EnvName, tt.fields.env)
|
2020-08-30 03:30:07 +00:00
|
|
|
|
2021-07-13 17:44:11 +00:00
|
|
|
got, found := p.findConfigFileFlag(tt.arg)
|
|
|
|
if got != tt.want {
|
|
|
|
t.Errorf("Parser.findConfigFileFlag() got = %+v\nWant = %+v", got, tt.want)
|
|
|
|
}
|
|
|
|
if found != tt.found {
|
|
|
|
t.Errorf("Parser.findConfigFileFlag() found = %+v\nWant = %+v", found, tt.found)
|
|
|
|
}
|
|
|
|
})
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 16:59:33 +00:00
|
|
|
func Test_UnitParser_Parse(t *testing.T) {
|
2020-08-30 03:30:07 +00:00
|
|
|
testDataOutput := []string{
|
2021-04-15 18:29:24 +00:00
|
|
|
"--foo-bar=bar-foo",
|
2023-05-02 18:18:23 +00:00
|
|
|
"--alice=bob",
|
2020-09-03 22:00:12 +00:00
|
|
|
"--a-slice=1",
|
2021-04-15 18:29:24 +00:00
|
|
|
"--a-slice=1.5",
|
2020-09-03 22:00:12 +00:00
|
|
|
"--a-slice=2",
|
|
|
|
"--a-slice=",
|
|
|
|
"--a-slice=three",
|
|
|
|
"--isempty=",
|
|
|
|
"-c=b",
|
|
|
|
"--isfalse=false",
|
|
|
|
"--islast=true",
|
2021-04-25 05:52:02 +00:00
|
|
|
"--b-string=one",
|
|
|
|
"--b-string=two",
|
|
|
|
"--c-slice=one",
|
|
|
|
"--c-slice=two",
|
|
|
|
"--c-slice=three",
|
|
|
|
"--d-slice=three",
|
|
|
|
"--d-slice=four",
|
2023-05-02 18:18:23 +00:00
|
|
|
"--f-string=beta",
|
2021-04-25 05:52:02 +00:00
|
|
|
"--e-slice=one",
|
|
|
|
"--e-slice=two",
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
2021-07-13 17:44:11 +00:00
|
|
|
type fields struct {
|
|
|
|
After []string
|
|
|
|
FlagNames []string
|
|
|
|
EnvName string
|
|
|
|
DefaultConfig string
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
2021-07-13 17:44:11 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
arg []string
|
|
|
|
want []string
|
|
|
|
wantErr bool
|
2020-08-30 03:30:07 +00:00
|
|
|
}{
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "default case",
|
|
|
|
fields: fields{
|
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "read config file when not specified",
|
|
|
|
fields: fields{
|
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
|
|
|
arg: []string{"server"},
|
|
|
|
want: append([]string{"server"}, testDataOutput...),
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "ignore missing config when not set",
|
|
|
|
fields: fields{
|
2020-08-30 03:30:07 +00:00
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
DefaultConfig: "missing",
|
|
|
|
},
|
2021-07-13 17:44:11 +00:00
|
|
|
arg: []string{"server"},
|
|
|
|
want: []string{"server"},
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "fail when missing config",
|
|
|
|
fields: fields{
|
2020-08-30 03:30:07 +00:00
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
DefaultConfig: "missing",
|
|
|
|
},
|
2021-07-13 17:44:11 +00:00
|
|
|
arg: []string{"server", "-c=missing"},
|
|
|
|
wantErr: true,
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "read config file",
|
|
|
|
fields: fields{
|
2020-08-30 03:30:07 +00:00
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
DefaultConfig: "missing",
|
|
|
|
},
|
2021-07-13 17:44:11 +00:00
|
|
|
arg: []string{"before", "server", "before", "-c", "./testdata/data.yaml", "after"},
|
|
|
|
want: append(append([]string{"before", "server"}, testDataOutput...), "before", "-c", "./testdata/data.yaml", "after"),
|
2020-08-30 03:30:07 +00:00
|
|
|
},
|
2021-04-15 18:29:24 +00:00
|
|
|
{
|
2021-07-13 17:44:11 +00:00
|
|
|
name: "read single config file",
|
|
|
|
fields: fields{
|
2021-04-15 18:29:24 +00:00
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
DefaultConfig: "missing",
|
|
|
|
},
|
2021-07-13 17:44:11 +00:00
|
|
|
arg: []string{"before", "server", "before", "-c", "./testdata/data.yaml.d/02-data.yaml", "after"},
|
|
|
|
want: []string{"before", "server",
|
2021-04-25 05:52:02 +00:00
|
|
|
"--foo-bar=bar-foo",
|
|
|
|
"--b-string=two",
|
|
|
|
"--c-slice=three",
|
|
|
|
"--d-slice=three",
|
|
|
|
"--d-slice=four",
|
|
|
|
"--e-slice=one",
|
|
|
|
"--e-slice=two",
|
|
|
|
"before", "-c", "./testdata/data.yaml.d/02-data.yaml", "after"},
|
2021-04-15 18:29:24 +00:00
|
|
|
},
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
2021-07-13 17:44:11 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
p := &Parser{
|
|
|
|
After: tt.fields.After,
|
2023-06-07 22:57:52 +00:00
|
|
|
ConfigFlags: tt.fields.FlagNames,
|
2021-07-13 17:44:11 +00:00
|
|
|
EnvName: tt.fields.EnvName,
|
|
|
|
DefaultConfig: tt.fields.DefaultConfig,
|
|
|
|
}
|
2020-08-30 03:30:07 +00:00
|
|
|
|
2021-07-13 17:44:11 +00:00
|
|
|
got, err := p.Parse(tt.arg)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("Parser.Parse() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("Parser.Parse() = %+v\nWant = %+v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 16:59:33 +00:00
|
|
|
func Test_UnitParser_FindString(t *testing.T) {
|
2021-07-13 17:44:11 +00:00
|
|
|
type fields struct {
|
|
|
|
After []string
|
|
|
|
FlagNames []string
|
|
|
|
EnvName string
|
|
|
|
DefaultConfig string
|
|
|
|
}
|
|
|
|
type args struct {
|
|
|
|
osArgs []string
|
|
|
|
target string
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
args args
|
|
|
|
want string
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Default config does not exist",
|
|
|
|
fields: fields{
|
|
|
|
After: []string{"server", "agent"},
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "missing",
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
osArgs: []string{},
|
|
|
|
target: "",
|
|
|
|
},
|
|
|
|
want: "",
|
|
|
|
},
|
|
|
|
{
|
2023-05-02 18:18:23 +00:00
|
|
|
name: "A custom config exists, target exists",
|
2021-07-13 17:44:11 +00:00
|
|
|
fields: fields{
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
osArgs: []string{"-c", "./testdata/data.yaml"},
|
2023-05-02 18:18:23 +00:00
|
|
|
target: "alice",
|
2021-07-13 17:44:11 +00:00
|
|
|
},
|
2023-05-02 18:18:23 +00:00
|
|
|
want: "bob",
|
2021-07-13 17:44:11 +00:00
|
|
|
},
|
|
|
|
{
|
2023-05-02 18:18:23 +00:00
|
|
|
name: "A custom config exists, target does not exist",
|
2021-07-13 17:44:11 +00:00
|
|
|
fields: fields{
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
osArgs: []string{"-c", "./testdata/data.yaml"},
|
|
|
|
target: "tls",
|
|
|
|
},
|
|
|
|
want: "",
|
|
|
|
},
|
2023-05-02 18:18:23 +00:00
|
|
|
{
|
|
|
|
name: "Multiple custom configs exist, target exists in a secondary config",
|
|
|
|
fields: fields{
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
osArgs: []string{"-c", "./testdata/data.yaml"},
|
|
|
|
target: "f-string",
|
|
|
|
},
|
|
|
|
want: "beta",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Multiple custom configs exist, multiple targets exist in multiple secondary config, replacement",
|
|
|
|
fields: fields{
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
osArgs: []string{"-c", "./testdata/data.yaml"},
|
|
|
|
target: "foo-bar",
|
|
|
|
},
|
|
|
|
want: "bar-foo",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Multiple custom configs exist, multiple targets exist in multiple secondary config, appending",
|
|
|
|
fields: fields{
|
|
|
|
FlagNames: []string{"-c", "--config"},
|
|
|
|
EnvName: "_TEST_ENV",
|
|
|
|
DefaultConfig: "./testdata/data.yaml",
|
|
|
|
},
|
|
|
|
args: args{
|
|
|
|
osArgs: []string{"-c", "./testdata/data.yaml"},
|
|
|
|
target: "b-string",
|
|
|
|
},
|
|
|
|
want: "one,two",
|
|
|
|
},
|
2021-07-13 17:44:11 +00:00
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
p := &Parser{
|
|
|
|
After: tt.fields.After,
|
2023-06-07 22:57:52 +00:00
|
|
|
ConfigFlags: tt.fields.FlagNames,
|
2021-07-13 17:44:11 +00:00
|
|
|
EnvName: tt.fields.EnvName,
|
|
|
|
DefaultConfig: tt.fields.DefaultConfig,
|
|
|
|
}
|
|
|
|
got, err := p.FindString(tt.args.osArgs, tt.args.target)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("Parser.FindString() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if got != tt.want {
|
|
|
|
t.Errorf("Parser.FindString() = %+v\nWant = %+v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
2020-08-30 03:30:07 +00:00
|
|
|
}
|
|
|
|
}
|