1// Copyright 2017 Unknwon
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15package ini_test
16
17import (
18	"bytes"
19	"io/ioutil"
20	"testing"
21
22	. "github.com/smartystreets/goconvey/convey"
23	"gopkg.in/ini.v1"
24)
25
26func TestEmpty(t *testing.T) {
27	Convey("Create an empty object", t, func() {
28		f := ini.Empty()
29		So(f, ShouldNotBeNil)
30
31		// Should only have the default section
32		So(len(f.Sections()), ShouldEqual, 1)
33
34		// Default section should not contain any key
35		So(len(f.Section("").Keys()), ShouldBeZeroValue)
36	})
37}
38
39func TestFile_NewSection(t *testing.T) {
40	Convey("Create a new section", t, func() {
41		f := ini.Empty()
42		So(f, ShouldNotBeNil)
43
44		sec, err := f.NewSection("author")
45		So(err, ShouldBeNil)
46		So(sec, ShouldNotBeNil)
47		So(sec.Name(), ShouldEqual, "author")
48
49		So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author"})
50
51		Convey("With duplicated name", func() {
52			sec, err := f.NewSection("author")
53			So(err, ShouldBeNil)
54			So(sec, ShouldNotBeNil)
55
56			// Does nothing if section already exists
57			So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author"})
58		})
59
60		Convey("With empty string", func() {
61			_, err := f.NewSection("")
62			So(err, ShouldNotBeNil)
63		})
64	})
65}
66
67func TestFile_NewRawSection(t *testing.T) {
68	Convey("Create a new raw section", t, func() {
69		f := ini.Empty()
70		So(f, ShouldNotBeNil)
71
72		sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
73111111111111111111100000000000111000000000`)
74		So(err, ShouldBeNil)
75		So(sec, ShouldNotBeNil)
76		So(sec.Name(), ShouldEqual, "comments")
77
78		So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "comments"})
79		So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000
80111111111111111111100000000000111000000000`)
81
82		Convey("With duplicated name", func() {
83			sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`)
84			So(err, ShouldBeNil)
85			So(sec, ShouldNotBeNil)
86			So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "comments"})
87
88			// Overwrite previous existed section
89			So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
90		})
91
92		Convey("With empty string", func() {
93			_, err := f.NewRawSection("", "")
94			So(err, ShouldNotBeNil)
95		})
96	})
97}
98
99func TestFile_NewSections(t *testing.T) {
100	Convey("Create new sections", t, func() {
101		f := ini.Empty()
102		So(f, ShouldNotBeNil)
103
104		So(f.NewSections("package", "author"), ShouldBeNil)
105		So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "package", "author"})
106
107		Convey("With duplicated name", func() {
108			So(f.NewSections("author", "features"), ShouldBeNil)
109
110			// Ignore section already exists
111			So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "package", "author", "features"})
112		})
113
114		Convey("With empty string", func() {
115			So(f.NewSections("", ""), ShouldNotBeNil)
116		})
117	})
118}
119
120func TestFile_GetSection(t *testing.T) {
121	Convey("Get a section", t, func() {
122		f, err := ini.Load(fullConf)
123		So(err, ShouldBeNil)
124		So(f, ShouldNotBeNil)
125
126		sec, err := f.GetSection("author")
127		So(err, ShouldBeNil)
128		So(sec, ShouldNotBeNil)
129		So(sec.Name(), ShouldEqual, "author")
130
131		Convey("Section not exists", func() {
132			_, err := f.GetSection("404")
133			So(err, ShouldNotBeNil)
134		})
135	})
136}
137
138func TestFile_Section(t *testing.T) {
139	Convey("Get a section", t, func() {
140		f, err := ini.Load(fullConf)
141		So(err, ShouldBeNil)
142		So(f, ShouldNotBeNil)
143
144		sec := f.Section("author")
145		So(sec, ShouldNotBeNil)
146		So(sec.Name(), ShouldEqual, "author")
147
148		Convey("Section not exists", func() {
149			sec := f.Section("404")
150			So(sec, ShouldNotBeNil)
151			So(sec.Name(), ShouldEqual, "404")
152		})
153	})
154
155	Convey("Get default section in lower case with insensitive load", t, func() {
156		f, err := ini.InsensitiveLoad([]byte(`
157[default]
158NAME = ini
159VERSION = v1`))
160		So(err, ShouldBeNil)
161		So(f, ShouldNotBeNil)
162
163		So(f.Section("").Key("name").String(), ShouldEqual, "ini")
164		So(f.Section("").Key("version").String(), ShouldEqual, "v1")
165	})
166}
167
168func TestFile_Sections(t *testing.T) {
169	Convey("Get all sections", t, func() {
170		f, err := ini.Load(fullConf)
171		So(err, ShouldBeNil)
172		So(f, ShouldNotBeNil)
173
174		secs := f.Sections()
175		names := []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}
176		So(len(secs), ShouldEqual, len(names))
177		for i, name := range names {
178			So(secs[i].Name(), ShouldEqual, name)
179		}
180	})
181}
182
183func TestFile_ChildSections(t *testing.T) {
184	Convey("Get child sections by parent name", t, func() {
185		f, err := ini.Load([]byte(`
186[node]
187[node.biz1]
188[node.biz2]
189[node.biz3]
190[node.bizN]
191`))
192		So(err, ShouldBeNil)
193		So(f, ShouldNotBeNil)
194
195		children := f.ChildSections("node")
196		names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"}
197		So(len(children), ShouldEqual, len(names))
198		for i, name := range names {
199			So(children[i].Name(), ShouldEqual, name)
200		}
201	})
202}
203
204func TestFile_SectionStrings(t *testing.T) {
205	Convey("Get all section names", t, func() {
206		f, err := ini.Load(fullConf)
207		So(err, ShouldBeNil)
208		So(f, ShouldNotBeNil)
209
210		So(f.SectionStrings(), ShouldResemble, []string{ini.DefaultSection, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"})
211	})
212}
213
214func TestFile_DeleteSection(t *testing.T) {
215	Convey("Delete a section", t, func() {
216		f := ini.Empty()
217		So(f, ShouldNotBeNil)
218
219		f.NewSections("author", "package", "features")
220		f.DeleteSection("features")
221		f.DeleteSection("")
222		So(f.SectionStrings(), ShouldResemble, []string{"author", "package"})
223	})
224}
225
226func TestFile_Append(t *testing.T) {
227	Convey("Append a data source", t, func() {
228		f := ini.Empty()
229		So(f, ShouldNotBeNil)
230
231		So(f.Append(minimalConf, []byte(`
232[author]
233NAME = Unknwon`)), ShouldBeNil)
234
235		Convey("With bad input", func() {
236			So(f.Append(123), ShouldNotBeNil)
237			So(f.Append(minimalConf, 123), ShouldNotBeNil)
238		})
239	})
240}
241
242func TestFile_WriteTo(t *testing.T) {
243	Convey("Write content to somewhere", t, func() {
244		f, err := ini.Load(fullConf)
245		So(err, ShouldBeNil)
246		So(f, ShouldNotBeNil)
247
248		f.Section("author").Comment = `Information about package author
249# Bio can be written in multiple lines.`
250		f.Section("author").Key("NAME").Comment = "This is author name"
251		f.Section("note").NewBooleanKey("boolean_key")
252		f.Section("note").NewKey("more", "notes")
253
254		var buf bytes.Buffer
255		_, err = f.WriteTo(&buf)
256		So(err, ShouldBeNil)
257
258		golden := "testdata/TestFile_WriteTo.golden"
259		if *update {
260			ioutil.WriteFile(golden, buf.Bytes(), 0644)
261		}
262
263		expected, err := ioutil.ReadFile(golden)
264		So(err, ShouldBeNil)
265		So(buf.String(), ShouldEqual, string(expected))
266	})
267
268	Convey("Support multiline comments", t, func() {
269		f, err := ini.Load([]byte(`
270#
271# general.domain
272#
273# Domain name of XX system.
274domain      = mydomain.com
275`))
276		So(err, ShouldBeNil)
277
278		f.Section("").Key("test").Comment = "Multiline\nComment"
279
280		var buf bytes.Buffer
281		_, err = f.WriteTo(&buf)
282		So(err, ShouldBeNil)
283
284		So(buf.String(), ShouldEqual, `#
285# general.domain
286#
287# Domain name of XX system.
288domain = mydomain.com
289; Multiline
290; Comment
291test   =
292
293`)
294
295	})
296}
297
298func TestFile_SaveTo(t *testing.T) {
299	Convey("Write content to somewhere", t, func() {
300		f, err := ini.Load(fullConf)
301		So(err, ShouldBeNil)
302		So(f, ShouldNotBeNil)
303
304		So(f.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
305		So(f.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
306	})
307}
308
309// Inspired by https://github.com/go-ini/ini/issues/207
310func TestReloadAfterShadowLoad(t *testing.T) {
311	Convey("Reload file after ShadowLoad", t, func() {
312		f, err := ini.ShadowLoad([]byte(`
313[slice]
314v = 1
315v = 2
316v = 3
317`))
318		So(err, ShouldBeNil)
319		So(f, ShouldNotBeNil)
320
321		So(f.Section("slice").Key("v").ValueWithShadows(), ShouldResemble, []string{"1", "2", "3"})
322
323		So(f.Reload(), ShouldBeNil)
324		So(f.Section("slice").Key("v").ValueWithShadows(), ShouldResemble, []string{"1", "2", "3"})
325	})
326}
327