1 | var test = require('tape')
|
2 | var contentType = require('content-type')
|
3 | var parsexml = require('xml-parser')
|
4 | var hyperquest = require('hyperquest')
|
5 | var concat = require('concat-stream')
|
6 | var isISODate = require('isostring')
|
7 |
|
8 | var base, server, changeId
|
9 |
|
10 | var createServer = require('./lib/test_server.js')
|
11 |
|
12 | test('changeset_upload_relation.js: setup server', function (t) {
|
13 | createServer(function (d) {
|
14 | base = d.base
|
15 | server = d.server
|
16 | t.end()
|
17 | })
|
18 | })
|
19 |
|
20 | test('create changeset upload', function (t) {
|
21 | t.plan(4)
|
22 | var href = base + 'changeset/create'
|
23 | var hq = hyperquest.put(href, {
|
24 | headers: { 'content-type': 'text/xml' }
|
25 | })
|
26 | hq.once('response', function (res) {
|
27 | t.equal(res.statusCode, 200, 'create 200 ok')
|
28 | var contentObj = contentType.parse(res)
|
29 | t.equal(contentObj.type, 'text/plain', 'media type correct')
|
30 | t.equal(contentObj.parameters.charset.toLowerCase(), 'utf-8', 'charset correct')
|
31 | })
|
32 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
33 | changeId = body.trim()
|
34 | t.ok(/^[0-9A-Fa-f]+$/.test(changeId), 'expected changeset id response')
|
35 | }))
|
36 | hq.end(`<osm>
|
37 | <changeset>
|
38 | <tag k="comment" v="wow"/>
|
39 | </changeset>
|
40 | </osm>`)
|
41 | })
|
42 |
|
43 | var ids = {}
|
44 | var versions = {}
|
45 | test('add docs to changeset upload', function (t) {
|
46 | t.plan(9)
|
47 |
|
48 | var href = base + 'changeset/' + changeId + '/upload'
|
49 | var hq = hyperquest.post(href, {
|
50 | headers: { 'content-type': 'text/xml' }
|
51 | })
|
52 | hq.on('response', function (res) {
|
53 | t.equal(res.statusCode, 200)
|
54 | var contentObj = contentType.parse(res)
|
55 | t.equal(contentObj.type, 'text/xml', 'media type correct')
|
56 | t.equal(contentObj.parameters.charset.toLowerCase(), 'utf-8', 'charset correct')
|
57 | })
|
58 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
59 | var xml = parsexml(body)
|
60 | t.equal(xml.root.name, 'diffResult')
|
61 | t.deepEqual(xml.root.children.map(function (c) {
|
62 | return c.attributes.old_id
|
63 | }).sort(), ['-1', '-2', '-3', '-4'])
|
64 | xml.root.children.forEach(function (c) {
|
65 | ids[c.attributes.old_id] = c.attributes.new_id
|
66 | t.notEqual(c.attributes.old_id, c.attributes.new_id,
|
67 | 'placeholder id should not equal new id')
|
68 | versions[c.attributes.old_id] = c.attributes.new_version
|
69 | })
|
70 | }))
|
71 | hq.end(`<osmChange version="1.0" generator="acme osm editor">
|
72 | <create>
|
73 | <node id="-1" changeset="${changeId}" lat="64.5" lon="-121.5"/>
|
74 | <node id="-2" changeset="${changeId}" lat="63.9" lon="-120.9"/>
|
75 | <way id="-3" changeset="${changeId}">
|
76 | <nd ref="-1"/>
|
77 | <nd ref="-2"/>
|
78 | </way>
|
79 | <relation id="-4" changeset="${changeId}">
|
80 | <tag k="hello" v="world" />
|
81 | <member type="way" ref="-3" role="" />
|
82 | </relation>
|
83 | </create>
|
84 | </osmChange>`)
|
85 | })
|
86 |
|
87 | test('get osmchange doc from upload', function (t) {
|
88 | t.plan(8)
|
89 | var expected = [
|
90 | {
|
91 | name: 'node',
|
92 | attributes: {
|
93 | changeset: changeId,
|
94 | id: ids['-1'],
|
95 | version: versions['-1'],
|
96 | lat: '64.5',
|
97 | lon: '-121.5'
|
98 | },
|
99 | children: []
|
100 | },
|
101 | {
|
102 | name: 'node',
|
103 | attributes: {
|
104 | changeset: changeId,
|
105 | id: ids['-2'],
|
106 | version: versions['-2'],
|
107 | lat: '63.9',
|
108 | lon: '-120.9'
|
109 | },
|
110 | children: []
|
111 | },
|
112 | {
|
113 | name: 'way',
|
114 | attributes: {
|
115 | changeset: changeId,
|
116 | id: ids['-3'],
|
117 | version: versions['-3']
|
118 | },
|
119 | content: '',
|
120 | children: [
|
121 | {
|
122 | name: 'nd',
|
123 | attributes: { ref: ids['-2'] },
|
124 | children: []
|
125 | },
|
126 | {
|
127 | name: 'nd',
|
128 | attributes: { ref: ids['-1'] },
|
129 | children: []
|
130 | }
|
131 | ].sort(cmpref)
|
132 | },
|
133 | {
|
134 | name: 'relation',
|
135 | attributes: {
|
136 | changeset: changeId,
|
137 | id: ids['-4'],
|
138 | version: versions['-4']
|
139 | },
|
140 | content: '',
|
141 | children: [
|
142 | {
|
143 | name: 'member',
|
144 | attributes: { type: 'way', ref: ids['-3'], role: '' },
|
145 | children: []
|
146 | },
|
147 | {
|
148 | name: 'tag',
|
149 | attributes: { k: 'hello', v: 'world' },
|
150 | children: []
|
151 | }
|
152 | ]
|
153 | }
|
154 | ].sort(cmpch)
|
155 | var href = base + 'changeset/' + changeId + '/download'
|
156 | var hq = hyperquest(href, {
|
157 | headers: { 'content-type': 'text/xml' }
|
158 | })
|
159 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
160 | var xml = parsexml(body)
|
161 | t.equal(xml.root.name, 'osmChange')
|
162 | t.equal(xml.root.children.length, 1)
|
163 | t.equal(xml.root.children[0].name, 'create')
|
164 | xml.root.children[0].children.sort(cmpch)
|
165 | xml.root.children[0].children.forEach(function (c) {
|
166 | c.children.sort(cmpref)
|
167 | })
|
168 | xml.root.children[0].children.forEach(function (c) {
|
169 | t.true(isISODate(c.attributes.timestamp))
|
170 | delete c.attributes.timestamp
|
171 | })
|
172 | t.deepEqual(xml.root.children[0].children, expected)
|
173 | }))
|
174 | })
|
175 |
|
176 | test('close changeset', function (t) {
|
177 | t.plan(2)
|
178 | var href = base + 'changeset/' + changeId + '/close'
|
179 | var hq = hyperquest.put(href)
|
180 | hq.once('response', function (res) {
|
181 | t.equal(res.statusCode, 200, 'create 200 ok')
|
182 | })
|
183 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
184 | t.equal(body.trim(), '', 'empty response')
|
185 | }))
|
186 | hq.end()
|
187 | })
|
188 |
|
189 | test('close already closed changeset', function (t) {
|
190 | t.plan(4)
|
191 | var href = base + 'changeset/' + changeId + '/close'
|
192 | var hq = hyperquest.put(href)
|
193 | hq.once('response', function (res) {
|
194 | t.equal(res.statusCode, 409, 'expected conflict code')
|
195 | var contentObj = contentType.parse(res)
|
196 | t.equal(contentObj.type, 'text/plain', 'media type correct')
|
197 | t.equal(contentObj.parameters.charset.toLowerCase(), 'utf-8', 'charset correct')
|
198 | })
|
199 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
200 | t.equal(
|
201 | body.trim().replace(/closed at.*/, 'closed at'),
|
202 | 'The changeset ' + changeId + ' was closed at',
|
203 | 'already closed message'
|
204 | )
|
205 | }))
|
206 | hq.end()
|
207 | })
|
208 |
|
209 | test('upload to closed changeset', function (t) {
|
210 | t.plan(4)
|
211 | var href = base + 'changeset/' + changeId + '/upload'
|
212 | var hq = hyperquest.post(href, {
|
213 | headers: { 'content-type': 'text/xml' }
|
214 | })
|
215 | hq.on('response', function (res) {
|
216 | t.equal(res.statusCode, 409, 'expected conflict code')
|
217 | var contentObj = contentType.parse(res)
|
218 | t.equal(contentObj.type, 'text/plain', 'media type correct')
|
219 | t.equal(contentObj.parameters.charset.toLowerCase(), 'utf-8', 'charset correct')
|
220 | })
|
221 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
222 | t.equal(
|
223 | body.trim().replace(/closed at.*/, 'closed at'),
|
224 | 'The changeset ' + changeId + ' was closed at',
|
225 | 'already closed message'
|
226 | )
|
227 | }))
|
228 | hq.end(`<osmChange version="1.0" generator="acme osm editor">
|
229 | <create>
|
230 | <node id="-1" changeset="${changeId}" lat="64.5" lon="-121.5"/>
|
231 | <node id="-2" changeset="${changeId}" lat="63.9" lon="-120.9"/>
|
232 | <way id="-3" changeset="${changeId}">
|
233 | <nd ref="-1"/>
|
234 | <nd ref="-2"/>
|
235 | </way>
|
236 | </create>
|
237 | </osmChange>`)
|
238 | })
|
239 |
|
240 | var secondChangeId
|
241 | test('create second changeset', function (t) {
|
242 | t.plan(4)
|
243 | var href = base + 'changeset/create'
|
244 | var hq = hyperquest.put(href, {
|
245 | headers: { 'content-type': 'text/xml' }
|
246 | })
|
247 | hq.once('response', function (res) {
|
248 | t.equal(res.statusCode, 200, 'create 200 ok')
|
249 | var contentObj = contentType.parse(res)
|
250 | t.equal(contentObj.type, 'text/plain', 'media type correct')
|
251 | t.equal(contentObj.parameters.charset.toLowerCase(), 'utf-8', 'charset correct')
|
252 | })
|
253 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
254 | secondChangeId = body.trim()
|
255 | t.ok(/^[0-9A-Fa-f]+$/.test(secondChangeId), 'expected changeset id response')
|
256 | }))
|
257 | hq.end(`<osm>
|
258 | <changeset>
|
259 | <tag k="comment" v="second"/>
|
260 | </changeset>
|
261 | </osm>`)
|
262 | })
|
263 |
|
264 | test('second changeset upload', function (t) {
|
265 | t.plan(13)
|
266 | var href = base + 'changeset/' + secondChangeId + '/upload'
|
267 | var hq = hyperquest.post(href, {
|
268 | headers: { 'content-type': 'text/xml' }
|
269 | })
|
270 | hq.on('response', function (res) {
|
271 | t.equal(res.statusCode, 200)
|
272 | var contentObj = contentType.parse(res)
|
273 | t.equal(contentObj.type, 'text/xml', 'media type correct')
|
274 | t.equal(contentObj.parameters.charset.toLowerCase(), 'utf-8', 'charset correct')
|
275 | })
|
276 | var oldv, newv
|
277 | hq.pipe(concat({ encoding: 'string' }, function (body) {
|
278 | var xml = parsexml(body)
|
279 | t.equal(xml.root.name, 'diffResult')
|
280 | t.deepEqual(xml.root.children.map(function (c) {
|
281 | return c.attributes.old_id
|
282 | }).sort(), [ids['-1']])
|
283 |
|
284 | oldv = versions['-1']
|
285 | newv = xml.root.children[0].attributes.new_version
|
286 | hyperquest.get(base + 'node/' + ids['-1'] + '/' + newv)
|
287 | .on('response', function (res) {
|
288 | t.equal(res.statusCode, 200)
|
289 | })
|
290 | .pipe(concat({ encoding: 'string' }, onnew))
|
291 | hyperquest.get(base + 'node/' + ids['-1'] + '/' + oldv)
|
292 | .on('response', function (res) {
|
293 | t.equal(res.statusCode, 200)
|
294 | })
|
295 | .pipe(concat({ encoding: 'string' }, onold))
|
296 | }))
|
297 | hq.end(`<osmChange version="1.0" generator="acme osm editor">
|
298 | <modify>
|
299 | <node id="${ids['-1']}" changeset="${secondChangeId}" lat="111" lon="222"/>
|
300 | </modify>
|
301 | </osmChange>`)
|
302 |
|
303 | function onnew (body) {
|
304 | var xml = parsexml(body)
|
305 | t.equal(xml.root.name, 'osm')
|
306 | t.true(isISODate(xml.root.children[0].attributes.timestamp))
|
307 | delete xml.root.children[0].attributes.timestamp
|
308 | t.deepEqual(xml.root.children, [
|
309 | {
|
310 | name: 'node',
|
311 | attributes: {
|
312 | changeset: secondChangeId,
|
313 | lat: '111',
|
314 | lon: '222',
|
315 | id: ids['-1'],
|
316 | version: newv
|
317 | },
|
318 | children: []
|
319 | }
|
320 | ])
|
321 | }
|
322 | function onold (body) {
|
323 | var xml = parsexml(body)
|
324 | t.equal(xml.root.name, 'osm')
|
325 | t.true(isISODate(xml.root.children[0].attributes.timestamp))
|
326 | delete xml.root.children[0].attributes.timestamp
|
327 | t.deepEqual(xml.root.children, [
|
328 | {
|
329 | name: 'node',
|
330 | attributes: {
|
331 | changeset: changeId,
|
332 | lat: '64.5',
|
333 | lon: '-121.5',
|
334 | id: ids['-1'],
|
335 | version: oldv
|
336 | },
|
337 | children: []
|
338 | }
|
339 | ])
|
340 | }
|
341 | })
|
342 |
|
343 | test('changeset_upload_relation.js: teardown server', function (t) {
|
344 | server.cleanup(function () {
|
345 | t.end()
|
346 | })
|
347 | })
|
348 |
|
349 | function cmpch (a, b) {
|
350 | return a.attributes.id < b.attributes.id ? -1 : 1
|
351 | }
|
352 |
|
353 | function cmpref (a, b) {
|
354 | if (a.name === 'tag' && b.name !== 'tag') {
|
355 | return 1
|
356 | }
|
357 | if (a.name !== 'tag' && b.name === 'tag') {
|
358 | return -1
|
359 | }
|
360 | if (a.name === 'tag' && b.name === 'tag') {
|
361 | return a.attributes.k < b.attributes.tag ? -1 : 1
|
362 | }
|
363 | return a.attributes.ref < b.attributes.ref ? -1 : 1
|
364 | }
|