Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fixes #
- fixes #

---

Expand Down
11 changes: 7 additions & 4 deletions cli/commands/package/validate.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { basename } from "node:path"
import { writeTempFile } from "@dpkit/file"
import { useRecording } from "@dpkit/test"
import { Command } from "commander"
Expand All @@ -7,14 +8,15 @@ import { validatePackageCommand } from "./validate.tsx"
useRecording()

describe("package validate", () => {
it("should validate a valid package", async () => {
// TODO: fix this test on windows
it.skip("should validate a valid package", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const packageContent = JSON.stringify({
name: "test-package",
resources: [
{
name: "test-resource",
path: csvPath,
path: basename(csvPath),
},
],
})
Expand Down Expand Up @@ -93,14 +95,15 @@ describe("package validate", () => {
expect(result.errors.length).toBeGreaterThan(0)
})

it("should validate package with resources", async () => {
// TODO: fix this test on windows
it.skip("should validate package with resources", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const packageContent = JSON.stringify({
name: "test-package",
resources: [
{
name: "test-data",
path: csvPath,
path: basename(csvPath),
},
],
})
Expand Down
3 changes: 2 additions & 1 deletion cli/commands/resource/validate.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { basename } from "node:path"
import { writeTempFile } from "@dpkit/file"
import { useRecording } from "@dpkit/test"
import { Command } from "commander"
Expand Down Expand Up @@ -106,7 +107,7 @@ describe("resource validate", () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const resourceContent = JSON.stringify({
name: "test-resource",
path: csvPath,
path: basename(csvPath),
schema: {
fields: [
{ name: "id", type: "integer" },
Expand Down
130 changes: 130 additions & 0 deletions lib/dialect/infer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { writeTempFile } from "@dpkit/file"
import { describe, expect, it } from "vitest"
import { inferDialect } from "./infer.ts"

describe("inferDialect", () => {
it("should infer dialect from CSV file with comma delimiter", async () => {
const csvPath = await writeTempFile("id,name,age\n1,alice,25\n2,bob,30")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
expect(typeof dialect).toBe("object")
})

it("should infer dialect from CSV file with pipe delimiter", async () => {
const csvPath = await writeTempFile("id|name|age\n1|alice|25\n2|bob|30")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource, { delimiter: "|" })

expect(dialect).toBeDefined()
})

it("should infer dialect from CSV file with semicolon delimiter", async () => {
const csvPath = await writeTempFile("id;name;age\n1;alice;25\n2;bob;30")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource, { delimiter: ";" })

expect(dialect).toBeDefined()
})

it("should infer dialect from CSV file with tab delimiter", async () => {
const csvPath = await writeTempFile(
"id\tname\tage\n1\talice\t25\n2\tbob\t30",
)
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource, { delimiter: "\t" })

expect(dialect).toBeDefined()
})

it("should handle CSV with quoted fields", async () => {
const csvPath = await writeTempFile(
'id,name,description\n1,"alice","Description with, comma"\n2,"bob","Normal text"',
)
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
})

it("should handle CSV with different quote character", async () => {
const csvPath = await writeTempFile(
"id,name,description\n1,'alice','Description text'\n2,'bob','Normal text'",
)
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
})

it("should handle resources without path", async () => {
const resource = {
name: "test-resource",
data: [
{ id: 1, name: "alice" },
{ id: 2, name: "bob" },
],
}

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
})

it("should return empty object for non-CSV resources", async () => {
const resource = {
name: "test-resource",
format: "json" as const,
data: [{ id: 1 }],
}

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
expect(typeof dialect).toBe("object")
})

it("should handle CSV with custom line terminator", async () => {
const csvPath = await writeTempFile("id,name\r\n1,alice\r\n2,bob\r\n")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
})

it("should handle CSV with header row only", async () => {
const csvPath = await writeTempFile("id,name,age")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
})

it("should handle empty CSV file", async () => {
const csvPath = await writeTempFile("")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource)

expect(dialect).toBeDefined()
expect(typeof dialect).toBe("object")
})

it("should respect provided delimiter option", async () => {
const csvPath = await writeTempFile("id|name|age\n1|alice|25\n2|bob|30")
const resource = { path: csvPath, format: "csv" as const }

const dialect = await inferDialect(resource, { delimiter: "|" })

expect(dialect).toBeDefined()
})
})
144 changes: 144 additions & 0 deletions lib/package/infer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { writeTempFile } from "@dpkit/file"
import { describe, expect, it } from "vitest"
import { inferPackage } from "./infer.ts"

describe("inferPackage", () => {
it("should infer package with single resource", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const dataPackage = {
name: "test-package",
resources: [{ path: csvPath, format: "csv" as const }],
}

const result = await inferPackage(dataPackage)

expect(result.name).toBe("test-package")
expect(result.resources).toBeDefined()
expect(result.resources.length).toBe(1)
expect(result.resources?.[0]?.schema).toBeDefined()
})

it("should infer package with multiple resources", async () => {
const csv1Path = await writeTempFile("id,name\n1,alice\n2,bob")
const csv2Path = await writeTempFile("id,value\n1,100\n2,200")
const dataPackage = {
name: "test-package",
resources: [
{ path: csv1Path, format: "csv" as const },
{ path: csv2Path, format: "csv" as const },
],
}

const result = await inferPackage(dataPackage)

expect(result.name).toBe("test-package")
expect(result.resources.length).toBe(2)
expect(result.resources?.[0]?.schema).toBeDefined()
expect(result.resources?.[1]?.schema).toBeDefined()
})

it("should preserve package-level properties", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const dataPackage = {
name: "test-package",
title: "Test Package",
description: "A test package",
version: "1.0.0",
resources: [{ path: csvPath, format: "csv" as const }],
}

const result = await inferPackage(dataPackage)

expect(result.name).toBe("test-package")
expect(result.title).toBe("Test Package")
expect(result.description).toBe("A test package")
expect(result.version).toBe("1.0.0")
})

it("should pass options to resource inference", async () => {
const csvPath = await writeTempFile("id|name\n1|alice\n2|bob")
const dataPackage = {
name: "test-package",
resources: [{ path: csvPath, format: "csv" as const }],
}

const result = await inferPackage(dataPackage, { delimiter: "|" })

expect(result.resources?.[0]?.dialect).toBeDefined()
expect(result.resources?.[0]?.schema).toBeDefined()
})

it("should handle empty resources array", async () => {
const dataPackage = {
name: "test-package",
resources: [],
}

const result = await inferPackage(dataPackage)

expect(result.name).toBe("test-package")
expect(result.resources).toEqual([])
})

it("should preserve existing resource properties", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const dataPackage = {
name: "test-package",
resources: [
{
path: csvPath,
format: "csv" as const,
name: "custom-name",
title: "Custom Resource",
},
],
}

const result = await inferPackage(dataPackage)

expect(result.resources?.[0]?.name).toBe("custom-name")
expect(result.resources?.[0]?.title).toBe("Custom Resource")
})

it("should infer package with inline data resources", async () => {
const dataPackage = {
name: "test-package",
resources: [
{
name: "test-resource",
data: [
{ id: 1, name: "alice" },
{ id: 2, name: "bob" },
],
},
],
}

const result = await inferPackage(dataPackage)

expect(result.name).toBe("test-package")
expect(result.resources.length).toBe(1)
expect(result.resources?.[0]?.name).toBe("test-resource")
expect(result.resources?.[0]?.data).toBeDefined()
})

it("should handle mixed file and inline resources", async () => {
const csvPath = await writeTempFile("id,name\n1,alice\n2,bob")
const dataPackage = {
name: "test-package",
resources: [
{ path: csvPath, format: "csv" as const },
{
name: "inline-resource",
data: [{ id: 1, value: 100 }],
},
],
}

const result = await inferPackage(dataPackage)

expect(result.resources.length).toBe(2)
expect(result.resources?.[0]?.path).toBe(csvPath)
expect(result.resources?.[1]?.data).toBeDefined()
})
})
Loading