@bytebrand/export-cars 1.26.46 → 1.26.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configs/ooyyo.config.d.ts +14 -0
- package/dist/configs/ooyyo.config.js +40 -6
- package/dist/configs/ooyyo.config.js.map +1 -1
- package/dist/exporters/ooyyo.exporter.d.ts +5 -4
- package/dist/exporters/ooyyo.exporter.js +45 -25
- package/dist/exporters/ooyyo.exporter.js.map +1 -1
- package/dist/formats/ooyyo-xml.builder.d.ts +7 -2
- package/dist/formats/ooyyo-xml.builder.js +59 -36
- package/dist/formats/ooyyo-xml.builder.js.map +1 -1
- package/dist/interfaces/export-item.interface.d.ts +30 -2
- package/dist/interfaces/params.interface.d.ts +3 -0
- package/dist/services/mapping.service.d.ts +2 -0
- package/dist/services/mapping.service.js +33 -13
- package/dist/services/mapping.service.js.map +1 -1
- package/dist/services/ooyyo-feed-run.d.ts +81 -0
- package/dist/services/ooyyo-feed-run.js +183 -0
- package/dist/services/ooyyo-feed-run.js.map +1 -0
- package/dist/services/ooyyo-feed-verify.d.ts +13 -0
- package/dist/services/ooyyo-feed-verify.js +104 -0
- package/dist/services/ooyyo-feed-verify.js.map +1 -0
- package/dist/transformers/ooyyo.transformer.d.ts +51 -11
- package/dist/transformers/ooyyo.transformer.js +268 -95
- package/dist/transformers/ooyyo.transformer.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/marktde/AUDIT.md +1 -1
- package/docs/marktde/RUNBOOK.md +3 -3
- package/docs/marktde/TRACKING.md +8 -2
- package/docs/meinestadt/PARTNER-QUESTIONS.md +17 -0
- package/docs/meinestadt/TRACKING.md +107 -0
- package/docs/ooyyo/AUDIT.md +81 -0
- package/docs/ooyyo/PARTNER-QUESTIONS.md +32 -0
- package/docs/ooyyo/RUNBOOK.md +67 -0
- package/docs/ooyyo/TRACKING.md +54 -0
- package/package.json +3 -2
- package/src/configs/ooyyo.config.ts +65 -15
- package/src/constants/fb.constants.ts +34 -34
- package/src/exporters/ooyyo.exporter.ts +75 -48
- package/src/formats/ooyyo-xml.builder.ts +87 -48
- package/src/interfaces/export-item.interface.ts +43 -2
- package/src/interfaces/params.interface.ts +5 -0
- package/src/services/mapping.service.ts +54 -13
- package/src/services/ooyyo-feed-run.ts +265 -0
- package/src/services/ooyyo-feed-verify.ts +131 -0
- package/src/transformers/ooyyo.transformer.ts +378 -109
- package/test/fixtures/ooyyo/OoyyoFormat.xml +125 -0
- package/test/fixtures/ooyyo/OoyyoFormat.xsd +116 -0
- package/test/fixtures/ooyyo.fixture.ts +53 -0
- package/test/mapping-csv.test.ts +65 -0
- package/test/ooyyo-feed-run.test.ts +293 -0
- package/test/ooyyo-mapping.test.ts +276 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// Run: npm test (ts-node, no test framework needed)
|
|
2
|
+
// OOYYO mapping, partner acceptance rules and XML structure (ADV-331), against the official
|
|
3
|
+
// OoyyoFormat.xsd / OoyyoFormat.xml in test/fixtures/ooyyo.
|
|
4
|
+
import * as assert from 'node:assert/strict';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
8
|
+
|
|
9
|
+
import { OoyyoXmlBuilder, cdata } from '../src/formats/ooyyo-xml.builder';
|
|
10
|
+
import {
|
|
11
|
+
OOYYO_LINK_PARAMS, OoyyoExclusion, getOoyyoContent, getOoyyoDateTime, getOoyyoEuronorm, getOoyyoPostcode, getOoyyoPrice,
|
|
12
|
+
getOoyyoYear, mapOoyyoItem, validateOoyyoItem,
|
|
13
|
+
} from '../src/transformers/ooyyo.transformer';
|
|
14
|
+
import {
|
|
15
|
+
OOYYO_AD_ELEMENTS, OOYYO_LOCATION_ELEMENTS, OOYYO_UNIT_ELEMENTS, verifyOoyyoXml,
|
|
16
|
+
} from '../src/services/ooyyo-feed-verify';
|
|
17
|
+
import { NOW, car, translate } from './fixtures/ooyyo.fixture';
|
|
18
|
+
|
|
19
|
+
const FIXTURES = join(__dirname, 'fixtures', 'ooyyo');
|
|
20
|
+
const IMAGES = ['https://images.auto.de/carimage/x/hero/large', 'https://images.auto.de/carimage/x/side/large'];
|
|
21
|
+
|
|
22
|
+
const map = (overrides: Record<string, any> = {}, ctx: Record<string, any> = {}) =>
|
|
23
|
+
mapOoyyoItem(car('abc-1', overrides), { domain: 'auto.de', images: IMAGES, translate, region: 'Sachsen', now: NOW, ...ctx });
|
|
24
|
+
|
|
25
|
+
const okItem = (overrides: Record<string, any> = {}) => {
|
|
26
|
+
const result = map(overrides);
|
|
27
|
+
assert.equal(result.ok, true, JSON.stringify(result));
|
|
28
|
+
return (result as any).item;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const reason = (overrides: Record<string, any> = {}, ctx: Record<string, any> = {}) => {
|
|
32
|
+
const result = map(overrides, ctx);
|
|
33
|
+
assert.equal(result.ok, false);
|
|
34
|
+
return (result as any).reason;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Element names / required unit attributes straight from the XSD file.
|
|
38
|
+
const xsd = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@' })
|
|
39
|
+
.parse(readFileSync(join(FIXTURES, 'OoyyoFormat.xsd'), 'utf8'))['xsd:schema'];
|
|
40
|
+
const xsdElements: any[] = xsd['xsd:element'];
|
|
41
|
+
const refsOf = (name: string): string[] => {
|
|
42
|
+
const el = xsdElements.find(e => e['@name'] === name);
|
|
43
|
+
return [].concat(el['xsd:complexType']['xsd:choice']['xsd:element']).map((e: any) => e['@ref']);
|
|
44
|
+
};
|
|
45
|
+
const unitRequired = xsdElements
|
|
46
|
+
.filter(e => {
|
|
47
|
+
const attr = e?.['xsd:complexType']?.['xsd:simpleContent']?.['xsd:extension']?.['xsd:attribute'];
|
|
48
|
+
return attr && attr['@name'] === 'unit' && attr['@use'] === 'required';
|
|
49
|
+
})
|
|
50
|
+
.map(e => e['@name']);
|
|
51
|
+
|
|
52
|
+
const tests: Array<[string, () => void]> = [
|
|
53
|
+
['element / unit lists of the verifier are exactly those of OoyyoFormat.xsd', () => {
|
|
54
|
+
assert.deepEqual(refsOf('ooyyo'), ['ad']);
|
|
55
|
+
assert.deepEqual(refsOf('ad'), OOYYO_AD_ELEMENTS);
|
|
56
|
+
assert.deepEqual(refsOf('location'), OOYYO_LOCATION_ELEMENTS);
|
|
57
|
+
assert.deepEqual(refsOf('pictures'), ['picture']);
|
|
58
|
+
assert.deepEqual(unitRequired.sort(), [...OOYYO_UNIT_ELEMENTS].sort());
|
|
59
|
+
}],
|
|
60
|
+
|
|
61
|
+
['full item: German copy, EUR integer price, units, string postcode with leading zero, Berlin date/time', () => {
|
|
62
|
+
const item = okItem();
|
|
63
|
+
assert.equal(item.url, `https://www.auto.de/search/vehicle/abc-1?${OOYYO_LINK_PARAMS}`);
|
|
64
|
+
assert.equal(item.title, 'Opel Astra 1.2 Turbo Edition');
|
|
65
|
+
assert.equal(item.content, 'Ausstattung\n* ABS\n* Klimaanlage\nFahrzeug in gutem Zustand – Prüfung bestanden.');
|
|
66
|
+
assert.equal(item.price, '15148');
|
|
67
|
+
assert.equal(item.priceUnit, 'EUR');
|
|
68
|
+
assert.equal(item.year, '2025');
|
|
69
|
+
assert.equal(item.mileage, '13000');
|
|
70
|
+
assert.equal(item.engine_size, '1199');
|
|
71
|
+
assert.equal(item.power, '85');
|
|
72
|
+
assert.equal(item.color, 'Rot');
|
|
73
|
+
assert.equal(item.body_type, 'Kombi');
|
|
74
|
+
assert.equal(item.fuel_type, 'Benzin');
|
|
75
|
+
assert.equal(item.transmission, 'Manuell');
|
|
76
|
+
assert.equal(item.euronorm, '6');
|
|
77
|
+
assert.deepEqual(item.location, { city: 'Leipzig', postcode: '04103', region: 'Sachsen' });
|
|
78
|
+
assert.equal(item.vin, 'WVWZZZ1KZAW000001');
|
|
79
|
+
assert.equal(item.is_new, '0');
|
|
80
|
+
assert.equal(item.date, '07/07/2026');
|
|
81
|
+
assert.equal(item.time, '09:13');
|
|
82
|
+
assert.deepEqual(item.pictures, IMAGES);
|
|
83
|
+
}],
|
|
84
|
+
|
|
85
|
+
['clickout URL: partner in source + identical utm_*, no click id, no duplicate params', () => {
|
|
86
|
+
const url = new URL(okItem().url);
|
|
87
|
+
assert.equal(url.protocol, 'https:');
|
|
88
|
+
assert.equal(url.pathname, '/search/vehicle/abc-1');
|
|
89
|
+
const keys = [...url.searchParams.keys()];
|
|
90
|
+
assert.equal(new Set(keys).size, keys.length);
|
|
91
|
+
assert.deepEqual(Object.fromEntries(url.searchParams), {
|
|
92
|
+
source: 'ooyyo', medium: 'listing', campaign: 'listing', utm_source: 'ooyyo', utm_medium: 'listing', utm_campaign: 'listing',
|
|
93
|
+
});
|
|
94
|
+
assert.ok(!/click|gclid|fbclid|ooyyo_id/i.test(keys.join(',')));
|
|
95
|
+
}],
|
|
96
|
+
|
|
97
|
+
['price: currentSalesPrice only, whole euros; leasing rate / dealer cost never used', () => {
|
|
98
|
+
assert.equal(getOoyyoPrice(26900), '26900');
|
|
99
|
+
assert.equal(getOoyyoPrice('26900'), '26900');
|
|
100
|
+
assert.equal(getOoyyoPrice(26900.4), '26900');
|
|
101
|
+
for (const bad of [null, undefined, '', 0, -1, 'abc', true]) {
|
|
102
|
+
assert.equal(getOoyyoPrice(bad), null);
|
|
103
|
+
}
|
|
104
|
+
assert.equal(okItem({ 'price.currentSalesPrice': 9990, 'rate.monthlyRate': 99 }).price, '9990');
|
|
105
|
+
assert.equal(reason({ 'price.currentSalesPrice': null, 'price.purchasePrice': 11000 }), OoyyoExclusion.noPrice);
|
|
106
|
+
}],
|
|
107
|
+
|
|
108
|
+
['year: first registration, else construction year; never invented (legacy: now - 2 months)', () => {
|
|
109
|
+
assert.equal(getOoyyoYear(car('a'), NOW), '2025');
|
|
110
|
+
assert.equal(getOoyyoYear(car('a', { 'mainData.firstRegistration': null, 'mainData.constructionYear': 2026 }), NOW), '2026');
|
|
111
|
+
assert.equal(getOoyyoYear(car('a', { 'mainData.firstRegistration': 0, 'mainData.constructionYear': null }), NOW), '');
|
|
112
|
+
// ms instead of s => year far in the future, not used
|
|
113
|
+
assert.equal(getOoyyoYear(car('a', { 'mainData.firstRegistration': 1750809600000, 'mainData.constructionYear': null }), NOW), '');
|
|
114
|
+
assert.equal(reason({ 'mainData.condition': 'selector_condition_new', 'mainData.firstRegistration': null, 'mainData.constructionYear': null }),
|
|
115
|
+
OoyyoExclusion.noYear);
|
|
116
|
+
const newCar = okItem({ 'mainData.condition': 'selector_condition_new', 'mainData.firstRegistration': null, 'mainData.mileage': 0, 'mainData.constructionYear': 2026 });
|
|
117
|
+
assert.equal(newCar.year, '2026');
|
|
118
|
+
assert.equal(newCar.is_new, '1');
|
|
119
|
+
assert.equal(newCar.mileage, '0');
|
|
120
|
+
}],
|
|
121
|
+
|
|
122
|
+
['make / model OOYYO cannot recognise are excluded (SONSTIGE fallback, empty)', () => {
|
|
123
|
+
assert.equal(reason({ 'mainData.model': 'SONSTIGE' }), OoyyoExclusion.noModel);
|
|
124
|
+
assert.equal(reason({ 'mainData.model': '' }), OoyyoExclusion.noModel);
|
|
125
|
+
assert.equal(reason({ 'mainData.make': '' }), OoyyoExclusion.noMake);
|
|
126
|
+
assert.equal(reason({ 'mainData.make': 'selector_unknown' }), OoyyoExclusion.noMake);
|
|
127
|
+
}],
|
|
128
|
+
|
|
129
|
+
['photos: at least one HTTPS photo, URLs <= 1,500 chars; vehicle URL <= 1,500 chars', () => {
|
|
130
|
+
assert.equal(reason({}, { images: [] }), OoyyoExclusion.noImage);
|
|
131
|
+
assert.equal(reason({}, { images: ['http://images.auto.de/a.jpg'] }), OoyyoExclusion.noImage);
|
|
132
|
+
const p1500 = `https://images.auto.de/${'a'.repeat(1500 - 'https://images.auto.de/'.length)}`;
|
|
133
|
+
assert.equal(p1500.length, 1500);
|
|
134
|
+
assert.equal(map({}, { images: [p1500] }).ok, true);
|
|
135
|
+
assert.equal(reason({}, { images: [`${p1500}b`] }), OoyyoExclusion.urlTooLong);
|
|
136
|
+
const fixed = `https://www.auto.de/search/vehicle/?${OOYYO_LINK_PARAMS}`.length;
|
|
137
|
+
assert.equal(map({ _id: 'x'.repeat(1500 - fixed) }).ok, true);
|
|
138
|
+
assert.equal(reason({ _id: 'x'.repeat(1501 - fixed) }), OoyyoExclusion.urlTooLong);
|
|
139
|
+
}],
|
|
140
|
+
|
|
141
|
+
['reserved / sold are not listed', () => {
|
|
142
|
+
assert.equal(reason({ 'offer.offerAvailability': 'selector_offerAvailability_sold' }), OoyyoExclusion.notAvailable);
|
|
143
|
+
assert.equal(reason({ 'offer.offerAvailability': 'selector_offerAvailability_reserved' }), OoyyoExclusion.notAvailable);
|
|
144
|
+
}],
|
|
145
|
+
|
|
146
|
+
['postcode as string: leading zero restored for DE, invalid dropped, foreign kept', () => {
|
|
147
|
+
assert.equal(getOoyyoPostcode(4103, 'DE'), '04103');
|
|
148
|
+
assert.equal(getOoyyoPostcode('04103', 'DE'), '04103');
|
|
149
|
+
assert.equal(getOoyyoPostcode(10115, 'de'), '10115');
|
|
150
|
+
assert.equal(getOoyyoPostcode(1, 'DE'), '');
|
|
151
|
+
assert.equal(getOoyyoPostcode(999, null), '');
|
|
152
|
+
assert.equal(getOoyyoPostcode(null, 'DE'), '');
|
|
153
|
+
assert.equal(getOoyyoPostcode('1010', 'AT'), '1010');
|
|
154
|
+
}],
|
|
155
|
+
|
|
156
|
+
['optional fields: unknown / 0 / invalid are left out, never "default" / "undefined" / fake VIN', () => {
|
|
157
|
+
assert.equal(getOoyyoEuronorm('selector_unknown'), '');
|
|
158
|
+
assert.equal(getOoyyoEuronorm('selector_emissionClass_euro6d_temp'), '6d-TEMP');
|
|
159
|
+
const item = okItem({
|
|
160
|
+
'mainData.doors': 0, 'driveSuspension.gears': null, 'engineData.cubicCapacity': null, 'metaData.vin': 'FAKEVIN',
|
|
161
|
+
'mainData.category': 'selector_unknown', 'mainData.condition': null, 'offer.offerDescription': null, 'metaData.creationDate': null,
|
|
162
|
+
});
|
|
163
|
+
for (const field of ['doors', 'gears', 'engine_size', 'vin', 'body_type', 'is_new', 'content', 'date', 'time']) {
|
|
164
|
+
assert.equal(item[field], '', field);
|
|
165
|
+
}
|
|
166
|
+
const xml = OoyyoXmlBuilder.toXml(item);
|
|
167
|
+
for (const tag of ['doors', 'gears', 'engine_size', 'vin', 'body_type', 'is_new', 'content', 'date', 'time']) {
|
|
168
|
+
assert.ok(!xml.includes(`<${tag}`), `${tag} left out`);
|
|
169
|
+
}
|
|
170
|
+
assert.ok(!/undefined|null|default/.test(xml));
|
|
171
|
+
}],
|
|
172
|
+
|
|
173
|
+
['date / time: DD/MM/YYYY and HH:MM (24 h) in Europe/Berlin, summer and winter', () => {
|
|
174
|
+
assert.deepEqual(getOoyyoDateTime(1783408438), { date: '07/07/2026', time: '09:13' });
|
|
175
|
+
assert.deepEqual(getOoyyoDateTime(Date.parse('2026-01-15T22:30:00Z') / 1000), { date: '15/01/2026', time: '23:30' });
|
|
176
|
+
assert.deepEqual(getOoyyoDateTime(Date.parse('2026-03-31T22:05:00Z') / 1000), { date: '01/04/2026', time: '00:05' });
|
|
177
|
+
assert.deepEqual(getOoyyoDateTime(null), { date: '', time: '' });
|
|
178
|
+
}],
|
|
179
|
+
|
|
180
|
+
['content: mobile.de markup to plain text, umlauts intact, nothing added', () => {
|
|
181
|
+
assert.equal(getOoyyoContent('**Top**\\\\Größe: L\\\\\\\\\\\\\\\\Ende'), 'Top\nGröße: L\n\nEnde');
|
|
182
|
+
assert.equal(getOoyyoContent(' '), '');
|
|
183
|
+
assert.equal(getOoyyoContent(undefined), '');
|
|
184
|
+
}],
|
|
185
|
+
|
|
186
|
+
['XML: CDATA-safe ("]]>" split, invalid control chars removed), UTF-8 umlauts, & unescaped in CDATA', () => {
|
|
187
|
+
assert.equal(cdata('a]]>b'), '<![CDATA[a]]]]><![CDATA[>b]]>');
|
|
188
|
+
assert.equal(cdata('a\u0001b\u000Bc\td'), '<![CDATA[abc\td]]>');
|
|
189
|
+
const item = okItem({ 'mainData.make': 'Škoda', 'mainData.model': 'Octavia', 'offer.offerDescription': 'Größe ]]> Ölwechsel\u0007 & Co' });
|
|
190
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<ooyyo>\n${OoyyoXmlBuilder.toXml(item)}</ooyyo>\n`;
|
|
191
|
+
const parsed = new XMLParser({ ignoreAttributes: false, parseTagValue: false }).parse(xml);
|
|
192
|
+
assert.equal(parsed.ooyyo.ad.content, 'Größe ]]> Ölwechsel & Co');
|
|
193
|
+
assert.equal(parsed.ooyyo.ad.make, 'Škoda');
|
|
194
|
+
assert.equal(parsed.ooyyo.ad.url, item.url);
|
|
195
|
+
assert.deepEqual(verifyOoyyoXml(xml, 1).issues, []);
|
|
196
|
+
}],
|
|
197
|
+
|
|
198
|
+
['XML structure: root as in the partner sample, price unit EUR, units on every unit element', () => {
|
|
199
|
+
const item = okItem();
|
|
200
|
+
const ad = OoyyoXmlBuilder.toXml(item);
|
|
201
|
+
assert.ok(ad.includes('<price unit="EUR"><![CDATA[15148]]></price>'));
|
|
202
|
+
assert.ok(ad.includes('<engine_size unit="cc">'));
|
|
203
|
+
assert.ok(ad.includes('<mileage unit="km">'));
|
|
204
|
+
assert.ok(ad.includes('<power unit="kw">'));
|
|
205
|
+
assert.ok(!ad.includes('unit="euro"'));
|
|
206
|
+
const sample = readFileSync(join(FIXTURES, 'OoyyoFormat.xml'), 'utf8');
|
|
207
|
+
assert.ok(sample.includes('<ooyyo xsi:noNamespaceSchemaLocation="OoyyoFormat.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'));
|
|
208
|
+
// element order as in the sample
|
|
209
|
+
const order = [...ad.matchAll(/^ {4}<([a-z_]+)/gm)].map(m => m[1]);
|
|
210
|
+
const sampleOrder = [...sample.split('</ad>')[0].matchAll(/^ {8}<([a-z_]+)/gm)].map(m => m[1]);
|
|
211
|
+
assert.deepEqual(order, sampleOrder.filter(t => order.includes(t)));
|
|
212
|
+
}],
|
|
213
|
+
|
|
214
|
+
['verifier rejects the legacy / partner-sample defects (unit euro, empty <power/>, noimage-only, no year)', () => {
|
|
215
|
+
const legacy = `<?xml version="1.0" encoding="UTF-8"?>
|
|
216
|
+
<ooyyo xsi:noNamespaceSchemaLocation="OoyyoFormat.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
217
|
+
<ad>
|
|
218
|
+
<url><![CDATA[https://www.autode-dev.de/search/vehicle/42c3?source=classifieds&medium=listing&campaign=ooyyo]]></url>
|
|
219
|
+
<content/>
|
|
220
|
+
<price unit="euro"><![CDATA[13348]]></price>
|
|
221
|
+
<make><![CDATA[MG]]></make>
|
|
222
|
+
<model><![CDATA[MG3]]></model>
|
|
223
|
+
<engine_size/>
|
|
224
|
+
<year><![CDATA[2025]]></year>
|
|
225
|
+
<euronorm><![CDATA[default]]></euronorm>
|
|
226
|
+
<pictures>
|
|
227
|
+
<picture><![CDATA[https://images.auto.de/carimage/noimage/7pNvWELuWhZX/large-cached.jpeg]]></picture>
|
|
228
|
+
</pictures>
|
|
229
|
+
</ad>
|
|
230
|
+
<ad>
|
|
231
|
+
<url><![CDATA[https://www.autode-dev.de/search/vehicle/42c4]]></url>
|
|
232
|
+
<price unit="EUR"><![CDATA[9000]]></price>
|
|
233
|
+
<make><![CDATA[MG]]></make>
|
|
234
|
+
<model><![CDATA[ZS]]></model>
|
|
235
|
+
<pictures><picture><![CDATA[https://images.auto.de/a.jpg]]></picture></pictures>
|
|
236
|
+
</ad>
|
|
237
|
+
</ooyyo>`;
|
|
238
|
+
const { ads, issues } = verifyOoyyoXml(legacy, 2);
|
|
239
|
+
assert.equal(ads, 2);
|
|
240
|
+
const problems = issues.map(i => `${i.ad}:${i.problem}`);
|
|
241
|
+
assert.ok(problems.some(p => p.startsWith('0:<price> unit="euro"')), problems.join('\n'));
|
|
242
|
+
assert.ok(problems.some(p => p.startsWith('0:<engine_size> unit=""')), problems.join('\n'));
|
|
243
|
+
assert.ok(problems.some(p => p === `0:rejected by partner rules: ${OoyyoExclusion.noPrice}`), problems.join('\n'));
|
|
244
|
+
assert.ok(problems.some(p => p === `1:rejected by partner rules: ${OoyyoExclusion.noYear}`), problems.join('\n'));
|
|
245
|
+
// empty unit-less / broken / foreign root files
|
|
246
|
+
assert.equal(verifyOoyyoXml('<ooyyo><ad></ooyyo>', 0).issues[0].ad, -1);
|
|
247
|
+
assert.match(verifyOoyyoXml('<feed></feed>', 0).issues[0].problem, /root element/);
|
|
248
|
+
assert.match(verifyOoyyoXml('<ooyyo></ooyyo>', 3).issues[0].problem, /holds 0 ads, the run wrote 3/);
|
|
249
|
+
}],
|
|
250
|
+
|
|
251
|
+
['validateOoyyoItem: the partner minimum (photo, year, price EUR, make, model, url)', () => {
|
|
252
|
+
const item = okItem();
|
|
253
|
+
assert.equal(validateOoyyoItem(item), null);
|
|
254
|
+
assert.equal(validateOoyyoItem({ ...item, priceUnit: 'euro' as any }), OoyyoExclusion.noPrice);
|
|
255
|
+
assert.equal(validateOoyyoItem({ ...item, price: '0' }), OoyyoExclusion.noPrice);
|
|
256
|
+
assert.equal(validateOoyyoItem({ ...item, year: '' }), OoyyoExclusion.noYear);
|
|
257
|
+
assert.equal(validateOoyyoItem({ ...item, pictures: [] }), OoyyoExclusion.noImage);
|
|
258
|
+
assert.equal(validateOoyyoItem({ ...item, url: 'http://www.auto.de/x' }), OoyyoExclusion.noId);
|
|
259
|
+
}],
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
let failed = 0;
|
|
263
|
+
for (const [name, fn] of tests) {
|
|
264
|
+
try {
|
|
265
|
+
fn();
|
|
266
|
+
console.log(`ok - ${name}`);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
failed += 1;
|
|
269
|
+
console.log(`not ok - ${name}\n${error?.stack ?? error}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (failed) {
|
|
273
|
+
console.log(`${failed} of ${tests.length} failed`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
console.log(`ooyyo-mapping: ${tests.length} passed`);
|