@bytebrand/export-cars 1.26.47 → 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/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 +84 -84
- package/docs/marktde/PARTNER-QUESTIONS.md +49 -49
- package/docs/marktde/RUNBOOK.md +58 -58
- package/docs/marktde/TRACKING.md +75 -69
- 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 +66 -65
- package/src/configs/marktde.config.ts +70 -70
- package/src/configs/ooyyo.config.ts +65 -15
- package/src/exporters/marktde.exporter.ts +78 -78
- 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/marktde-feed-run.ts +233 -233
- package/src/services/ooyyo-feed-run.ts +265 -0
- package/src/services/ooyyo-feed-verify.ts +131 -0
- package/src/transformers/marktde.transformer.ts +255 -255
- 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 -65
- package/test/marktde-feed-run.test.ts +263 -263
- package/test/marktde-mapping.test.ts +280 -280
- package/test/ooyyo-feed-run.test.ts +293 -0
- package/test/ooyyo-mapping.test.ts +276 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<xsd:schema elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
3
|
+
<xsd:element name="ooyyo">
|
|
4
|
+
<xsd:complexType>
|
|
5
|
+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
6
|
+
<xsd:element ref="ad"/>
|
|
7
|
+
</xsd:choice>
|
|
8
|
+
</xsd:complexType>
|
|
9
|
+
</xsd:element>
|
|
10
|
+
<xsd:element name="ad">
|
|
11
|
+
<xsd:complexType mixed="true">
|
|
12
|
+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
13
|
+
<xsd:element ref="url"/>
|
|
14
|
+
<xsd:element ref="title"/>
|
|
15
|
+
<xsd:element ref="content"/>
|
|
16
|
+
<xsd:element ref="price"/>
|
|
17
|
+
<xsd:element ref="make"/>
|
|
18
|
+
<xsd:element ref="model"/>
|
|
19
|
+
<xsd:element ref="engine_size"/>
|
|
20
|
+
<xsd:element ref="trim"/>
|
|
21
|
+
<xsd:element ref="body_type"/>
|
|
22
|
+
<xsd:element ref="color"/>
|
|
23
|
+
<xsd:element ref="year"/>
|
|
24
|
+
<xsd:element ref="mileage"/>
|
|
25
|
+
<xsd:element ref="fuel_type"/>
|
|
26
|
+
<xsd:element ref="transmission"/>
|
|
27
|
+
<xsd:element ref="euronorm"/>
|
|
28
|
+
<xsd:element ref="power"/>
|
|
29
|
+
<xsd:element ref="doors"/>
|
|
30
|
+
<xsd:element ref="seats"/>
|
|
31
|
+
<xsd:element ref="gears"/>
|
|
32
|
+
<xsd:element ref="location"/>
|
|
33
|
+
<xsd:element ref="pictures"/>
|
|
34
|
+
<xsd:element ref="vin"/>
|
|
35
|
+
<xsd:element ref="is_new"/>
|
|
36
|
+
<xsd:element ref="date"/>
|
|
37
|
+
<xsd:element ref="time"/>
|
|
38
|
+
</xsd:choice>
|
|
39
|
+
</xsd:complexType>
|
|
40
|
+
</xsd:element>
|
|
41
|
+
<xsd:element name="url" type="xsd:string"/>
|
|
42
|
+
<xsd:element name="title" type="xsd:string"/>
|
|
43
|
+
<xsd:element name="content" type="xsd:string"/>
|
|
44
|
+
<xsd:element name="price">
|
|
45
|
+
<xsd:complexType>
|
|
46
|
+
<xsd:simpleContent>
|
|
47
|
+
<xsd:extension base="xsd:string">
|
|
48
|
+
<xsd:attribute name="unit" type="xsd:string" use="required"/>
|
|
49
|
+
</xsd:extension>
|
|
50
|
+
</xsd:simpleContent>
|
|
51
|
+
</xsd:complexType>
|
|
52
|
+
</xsd:element>
|
|
53
|
+
<xsd:element name="make" type="xsd:string"/>
|
|
54
|
+
<xsd:element name="model" type="xsd:string"/>
|
|
55
|
+
<xsd:element name="engine_size">
|
|
56
|
+
<xsd:complexType>
|
|
57
|
+
<xsd:simpleContent>
|
|
58
|
+
<xsd:extension base="xsd:string">
|
|
59
|
+
<xsd:attribute name="unit" type="xsd:string" use="required"/>
|
|
60
|
+
</xsd:extension>
|
|
61
|
+
</xsd:simpleContent>
|
|
62
|
+
</xsd:complexType>
|
|
63
|
+
</xsd:element>
|
|
64
|
+
<xsd:element name="trim" type="xsd:string"/>
|
|
65
|
+
<xsd:element name="body_type" type="xsd:string"/>
|
|
66
|
+
<xsd:element name="color" type="xsd:string"/>
|
|
67
|
+
<xsd:element name="year" type="xsd:string"/>
|
|
68
|
+
<xsd:element name="mileage">
|
|
69
|
+
<xsd:complexType>
|
|
70
|
+
<xsd:simpleContent>
|
|
71
|
+
<xsd:extension base="xsd:string">
|
|
72
|
+
<xsd:attribute name="unit" type="xsd:string" use="required"/>
|
|
73
|
+
</xsd:extension>
|
|
74
|
+
</xsd:simpleContent>
|
|
75
|
+
</xsd:complexType>
|
|
76
|
+
</xsd:element>
|
|
77
|
+
<xsd:element name="fuel_type" type="xsd:string"/>
|
|
78
|
+
<xsd:element name="transmission" type="xsd:string"/>
|
|
79
|
+
<xsd:element name="euronorm" type="xsd:string"/>
|
|
80
|
+
<xsd:element name="power">
|
|
81
|
+
<xsd:complexType>
|
|
82
|
+
<xsd:simpleContent>
|
|
83
|
+
<xsd:extension base="xsd:string">
|
|
84
|
+
<xsd:attribute name="unit" type="xsd:string" use="required"/>
|
|
85
|
+
</xsd:extension>
|
|
86
|
+
</xsd:simpleContent>
|
|
87
|
+
</xsd:complexType>
|
|
88
|
+
</xsd:element>
|
|
89
|
+
<xsd:element name="doors" type="xsd:string"/>
|
|
90
|
+
<xsd:element name="seats" type="xsd:string"/>
|
|
91
|
+
<xsd:element name="gears" type="xsd:string"/>
|
|
92
|
+
<xsd:element name="location">
|
|
93
|
+
<xsd:complexType mixed="true">
|
|
94
|
+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
95
|
+
<xsd:element ref="city"/>
|
|
96
|
+
<xsd:element ref="postcode"/>
|
|
97
|
+
<xsd:element ref="region"/>
|
|
98
|
+
</xsd:choice>
|
|
99
|
+
</xsd:complexType>
|
|
100
|
+
</xsd:element>
|
|
101
|
+
<xsd:element name="city" type="xsd:string"/>
|
|
102
|
+
<xsd:element name="postcode" type="xsd:string"/>
|
|
103
|
+
<xsd:element name="region" type="xsd:string"/>
|
|
104
|
+
<xsd:element name="pictures">
|
|
105
|
+
<xsd:complexType mixed="true">
|
|
106
|
+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
|
107
|
+
<xsd:element ref="picture"/>
|
|
108
|
+
</xsd:choice>
|
|
109
|
+
</xsd:complexType>
|
|
110
|
+
</xsd:element>
|
|
111
|
+
<xsd:element name="picture" type="xsd:string"/>
|
|
112
|
+
<xsd:element name="vin" type="xsd:string"/>
|
|
113
|
+
<xsd:element name="is_new" type="xsd:string"/>
|
|
114
|
+
<xsd:element name="date" type="xsd:string"/>
|
|
115
|
+
<xsd:element name="time" type="xsd:string"/>
|
|
116
|
+
</xsd:schema>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { car as baseCar, images } from './meinstadt.fixture';
|
|
2
|
+
|
|
3
|
+
export { NOW, images } from './meinstadt.fixture';
|
|
4
|
+
|
|
5
|
+
// German labels as @bytebrand/i18n-dictionaries `de` returns them (OoyyoTransformer.translate).
|
|
6
|
+
const LABELS: Record<string, string> = {
|
|
7
|
+
selector_exteriorColor_red: 'Außenfarbe Rot',
|
|
8
|
+
selector_category_combi: 'Kombi',
|
|
9
|
+
selector_category_suv: 'SUV/Geländewagen',
|
|
10
|
+
selector_fuel_petrol: 'Benzin',
|
|
11
|
+
selector_fuel_electrical: 'Elektro',
|
|
12
|
+
selector_gearbox_manualShift: 'Manuell',
|
|
13
|
+
selector_gearbox_automatic: 'Automatik',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const translate = (selector: string): string => LABELS[selector] ?? '';
|
|
17
|
+
|
|
18
|
+
export const region = async (postcode: string): Promise<string> =>
|
|
19
|
+
({ '04103': 'Sachsen', '10115': 'Berlin', '80331': 'Bayern' } as Record<string, string>)[postcode] ?? '';
|
|
20
|
+
|
|
21
|
+
// A complete, sellable OOYYO car (CBD shape as the base export delivers it).
|
|
22
|
+
export const car = (id: string, overrides: Record<string, any> = {}): any => baseCar(id, {
|
|
23
|
+
'metaData.creationDate': 1783408438, // 07.07.2026 07:13:58 UTC = 09:13 Berlin
|
|
24
|
+
'metaData.vin': 'WVWZZZ1KZAW000001',
|
|
25
|
+
'mainData.constructionYear': 2024,
|
|
26
|
+
'engineData.cubicCapacity': 1199,
|
|
27
|
+
'driveSuspension.gears': 6,
|
|
28
|
+
'location.zipCode': 4103,
|
|
29
|
+
'location.city': 'Leipzig',
|
|
30
|
+
'location.countryCode': 'DE',
|
|
31
|
+
'offer.offerDescription': '**Ausstattung**\\\\* ABS\\\\* Klimaanlage\\\\Fahrzeug in gutem Zustand – Prüfung bestanden.',
|
|
32
|
+
...overrides,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const source = (id: string, overrides: Record<string, any> = {}) => ({ car: car(id, overrides), images: images(id) });
|
|
36
|
+
|
|
37
|
+
// 20 representative cars: makes, used/new, petrol/BEV, postcodes with and without leading zero.
|
|
38
|
+
export const twentyCars = () => Array.from({ length: 20 }, (_, i) => {
|
|
39
|
+
const id = `car${String(i).padStart(2, '0')}`;
|
|
40
|
+
const variants: Record<string, any>[] = [
|
|
41
|
+
{},
|
|
42
|
+
{ 'mainData.make': 'Škoda', 'mainData.model': 'Octavia', 'mainData.subModel': 'Combi 2.0 TDI Größe L' },
|
|
43
|
+
{ 'mainData.condition': 'selector_condition_new', 'mainData.firstRegistration': null, 'mainData.mileage': 0, 'mainData.constructionYear': 2026 },
|
|
44
|
+
{ 'consumption.fuel': 'selector_fuel_electrical', 'engineData.cubicCapacity': null, 'mainData.make': 'Tesla', 'mainData.model': 'Model 3' },
|
|
45
|
+
{ 'location.zipCode': 10115, 'location.city': 'Berlin' },
|
|
46
|
+
{ 'location.zipCode': 80331, 'location.city': 'München', 'mainData.category': 'selector_category_suv' },
|
|
47
|
+
{ 'environmentEmissions.emissionClass': 'selector_unknown' },
|
|
48
|
+
{ 'offer.offerDescription': '' },
|
|
49
|
+
{ 'metaData.vin': 'FAKEVIN' },
|
|
50
|
+
{ 'driveSuspension.gearbox': 'selector_gearbox_automatic', 'price.currentSalesPrice': 26900 },
|
|
51
|
+
];
|
|
52
|
+
return source(id, variants[i % variants.length]);
|
|
53
|
+
});
|
package/test/mapping-csv.test.ts
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
// Run: npm test (ts-node, no test framework needed)
|
|
2
|
-
// Mapping rules from the public CSV of the mapping sheet tab (no Sheets API key) — pure, no I/O.
|
|
3
|
-
import * as assert from 'node:assert/strict';
|
|
4
|
-
|
|
5
|
-
import { MappingService, mappingSheetCsvUrl } from '../src/services/mapping.service';
|
|
6
|
-
|
|
7
|
-
const service = new MappingService({ config: {} } as any);
|
|
8
|
-
|
|
9
|
-
// Excerpt of the tab mobile_yescar as gviz returns it (13.11.2026): quoted cells, an unnamed column,
|
|
10
|
-
// trailing empty columns, selector rows that group onto the first row of their importField.
|
|
11
|
-
const CSV = [
|
|
12
|
-
'"importField","importFieldType","autodeField","autodeFieldType","importFieldSelector","autodeFieldSelector","","custom","",""',
|
|
13
|
-
'"[\'0\']","string","persistentId","string","","","","yes","",""',
|
|
14
|
-
'"[\'2\']","categoryOrClass_selector","mainData.category","selector","cabrio/roadster","selector_category_convertible","note","","",""',
|
|
15
|
-
'"[\'2\']","categoryOrClass_selector","mainData.category","selector","Kombi, Van","selector_category_combi","","","",""',
|
|
16
|
-
'"[\'3\']","string","mainData.make","string","","","","","",""',
|
|
17
|
-
'"[\'110\']","selector","driveSuspension.gearbox","selector","1","selector_gearbox_manualShift","","","",""',
|
|
18
|
-
'"[\'110\']","selector","driveSuspension.gearbox","selector","3","selector_gearbox_automatic","","","",""',
|
|
19
|
-
'"[\'999\']","string","","string","","","","","",""',
|
|
20
|
-
'',
|
|
21
|
-
].join('\n');
|
|
22
|
-
|
|
23
|
-
const tests: Array<[string, () => void]> = [
|
|
24
|
-
['url: gviz CSV of the tab by title, title URL-encoded, no key', () => {
|
|
25
|
-
assert.equal(
|
|
26
|
-
mappingSheetCsvUrl('147qz9sTgs1wi13ua8YKa0IgfxAn1X_qH6LkGnAnzeyY', 'mobile_yescar'),
|
|
27
|
-
'https://docs.google.com/spreadsheets/d/147qz9sTgs1wi13ua8YKa0IgfxAn1X_qH6LkGnAnzeyY/gviz/tq?tqx=out:csv&sheet=mobile_yescar',
|
|
28
|
-
);
|
|
29
|
-
assert.ok(mappingSheetCsvUrl('x', 'a b&c').endsWith('sheet=a%20b%26c'));
|
|
30
|
-
}],
|
|
31
|
-
|
|
32
|
-
['rules: same post-processing as the Sheets API rows (empty keys dropped, selectors grouped, rows without autodeField out)', () => {
|
|
33
|
-
const rules = service.rulesFromCsv(CSV, 'mobile_yescar') as any[];
|
|
34
|
-
assert.deepEqual(rules.map(r => r.importField), ["['0']", "['2']", "['3']", "['110']"]);
|
|
35
|
-
assert.deepEqual(rules[0], { importField: "['0']", importFieldType: 'string', autodeField: 'persistentId', autodeFieldType: 'string', custom: 'yes' });
|
|
36
|
-
assert.deepEqual(rules[1].selectors, ['cabrio/roadster:selector_category_convertible', 'Kombi, Van:selector_category_combi']);
|
|
37
|
-
assert.equal(rules[1].importFieldSelector, undefined);
|
|
38
|
-
assert.equal(rules[1][''], undefined, 'unnamed column is not part of the rule');
|
|
39
|
-
assert.deepEqual(rules[3].selectors, ['1:selector_gearbox_manualShift', '3:selector_gearbox_automatic']);
|
|
40
|
-
assert.deepEqual(Object.keys(rules[2]).sort(), ['autodeField', 'autodeFieldType', 'importField', 'importFieldType']);
|
|
41
|
-
}],
|
|
42
|
-
|
|
43
|
-
['not the tab (sign-in page / other table) -> error, never an empty mapping', () => {
|
|
44
|
-
assert.throws(() => service.rulesFromCsv('<!DOCTYPE html><html><body>Sign in</body></html>', 'mobile_yescar'), /not readable as CSV.*shared/);
|
|
45
|
-
assert.throws(() => service.rulesFromCsv('', 'mobile_yescar'), /not readable as CSV/);
|
|
46
|
-
assert.throws(() => service.rulesFromCsv('"a","b"\n"1","2"\n', 'mobile_yescar'), /missing columns importField/);
|
|
47
|
-
}],
|
|
48
|
-
|
|
49
|
-
['a tab with the columns but no usable rule -> error', () => {
|
|
50
|
-
const header = '"importField","importFieldType","autodeField","autodeFieldType"';
|
|
51
|
-
assert.throws(() => service.rulesFromCsv(`${header}\n"[\'1\']","string","",""\n`, 'mobile'), /has no rule/);
|
|
52
|
-
}],
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
let failed = 0;
|
|
56
|
-
for (const [name, fn] of tests) {
|
|
57
|
-
try {
|
|
58
|
-
fn();
|
|
59
|
-
console.log(`ok ${name}`);
|
|
60
|
-
} catch (e) {
|
|
61
|
-
failed += 1;
|
|
62
|
-
console.error(`FAIL ${name}\n ${e.message}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (failed) process.exit(1);
|
|
1
|
+
// Run: npm test (ts-node, no test framework needed)
|
|
2
|
+
// Mapping rules from the public CSV of the mapping sheet tab (no Sheets API key) — pure, no I/O.
|
|
3
|
+
import * as assert from 'node:assert/strict';
|
|
4
|
+
|
|
5
|
+
import { MappingService, mappingSheetCsvUrl } from '../src/services/mapping.service';
|
|
6
|
+
|
|
7
|
+
const service = new MappingService({ config: {} } as any);
|
|
8
|
+
|
|
9
|
+
// Excerpt of the tab mobile_yescar as gviz returns it (13.11.2026): quoted cells, an unnamed column,
|
|
10
|
+
// trailing empty columns, selector rows that group onto the first row of their importField.
|
|
11
|
+
const CSV = [
|
|
12
|
+
'"importField","importFieldType","autodeField","autodeFieldType","importFieldSelector","autodeFieldSelector","","custom","",""',
|
|
13
|
+
'"[\'0\']","string","persistentId","string","","","","yes","",""',
|
|
14
|
+
'"[\'2\']","categoryOrClass_selector","mainData.category","selector","cabrio/roadster","selector_category_convertible","note","","",""',
|
|
15
|
+
'"[\'2\']","categoryOrClass_selector","mainData.category","selector","Kombi, Van","selector_category_combi","","","",""',
|
|
16
|
+
'"[\'3\']","string","mainData.make","string","","","","","",""',
|
|
17
|
+
'"[\'110\']","selector","driveSuspension.gearbox","selector","1","selector_gearbox_manualShift","","","",""',
|
|
18
|
+
'"[\'110\']","selector","driveSuspension.gearbox","selector","3","selector_gearbox_automatic","","","",""',
|
|
19
|
+
'"[\'999\']","string","","string","","","","","",""',
|
|
20
|
+
'',
|
|
21
|
+
].join('\n');
|
|
22
|
+
|
|
23
|
+
const tests: Array<[string, () => void]> = [
|
|
24
|
+
['url: gviz CSV of the tab by title, title URL-encoded, no key', () => {
|
|
25
|
+
assert.equal(
|
|
26
|
+
mappingSheetCsvUrl('147qz9sTgs1wi13ua8YKa0IgfxAn1X_qH6LkGnAnzeyY', 'mobile_yescar'),
|
|
27
|
+
'https://docs.google.com/spreadsheets/d/147qz9sTgs1wi13ua8YKa0IgfxAn1X_qH6LkGnAnzeyY/gviz/tq?tqx=out:csv&sheet=mobile_yescar',
|
|
28
|
+
);
|
|
29
|
+
assert.ok(mappingSheetCsvUrl('x', 'a b&c').endsWith('sheet=a%20b%26c'));
|
|
30
|
+
}],
|
|
31
|
+
|
|
32
|
+
['rules: same post-processing as the Sheets API rows (empty keys dropped, selectors grouped, rows without autodeField out)', () => {
|
|
33
|
+
const rules = service.rulesFromCsv(CSV, 'mobile_yescar') as any[];
|
|
34
|
+
assert.deepEqual(rules.map(r => r.importField), ["['0']", "['2']", "['3']", "['110']"]);
|
|
35
|
+
assert.deepEqual(rules[0], { importField: "['0']", importFieldType: 'string', autodeField: 'persistentId', autodeFieldType: 'string', custom: 'yes' });
|
|
36
|
+
assert.deepEqual(rules[1].selectors, ['cabrio/roadster:selector_category_convertible', 'Kombi, Van:selector_category_combi']);
|
|
37
|
+
assert.equal(rules[1].importFieldSelector, undefined);
|
|
38
|
+
assert.equal(rules[1][''], undefined, 'unnamed column is not part of the rule');
|
|
39
|
+
assert.deepEqual(rules[3].selectors, ['1:selector_gearbox_manualShift', '3:selector_gearbox_automatic']);
|
|
40
|
+
assert.deepEqual(Object.keys(rules[2]).sort(), ['autodeField', 'autodeFieldType', 'importField', 'importFieldType']);
|
|
41
|
+
}],
|
|
42
|
+
|
|
43
|
+
['not the tab (sign-in page / other table) -> error, never an empty mapping', () => {
|
|
44
|
+
assert.throws(() => service.rulesFromCsv('<!DOCTYPE html><html><body>Sign in</body></html>', 'mobile_yescar'), /not readable as CSV.*shared/);
|
|
45
|
+
assert.throws(() => service.rulesFromCsv('', 'mobile_yescar'), /not readable as CSV/);
|
|
46
|
+
assert.throws(() => service.rulesFromCsv('"a","b"\n"1","2"\n', 'mobile_yescar'), /missing columns importField/);
|
|
47
|
+
}],
|
|
48
|
+
|
|
49
|
+
['a tab with the columns but no usable rule -> error', () => {
|
|
50
|
+
const header = '"importField","importFieldType","autodeField","autodeFieldType"';
|
|
51
|
+
assert.throws(() => service.rulesFromCsv(`${header}\n"[\'1\']","string","",""\n`, 'mobile'), /has no rule/);
|
|
52
|
+
}],
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
let failed = 0;
|
|
56
|
+
for (const [name, fn] of tests) {
|
|
57
|
+
try {
|
|
58
|
+
fn();
|
|
59
|
+
console.log(`ok ${name}`);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
failed += 1;
|
|
62
|
+
console.error(`FAIL ${name}\n ${e.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (failed) process.exit(1);
|