Skip to content
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
8 changes: 4 additions & 4 deletions backend/src/api/wikibase/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ export const wikibaseEntitiesApi = new Elysia({ prefix: '/api/wikibase/entities'

.get(
'/properties/:propertyId',
async ({ params: { propertyId }, query: { instance = 'wikidata' }, wikibase }) => {
async ({ params: { propertyId }, query: { instance = 'wikidata' }, wikibase, status }) => {
const property = await wikibase.getProperty(instance, propertyId)
if (!property) {
return ApiErrorHandler.notFoundError(`Property ${propertyId} not found`)
return status(404, ApiErrorHandler.notFoundError('Property', propertyId))
}
return { data: property }
},
Expand Down Expand Up @@ -89,10 +89,10 @@ export const wikibaseEntitiesApi = new Elysia({ prefix: '/api/wikibase/entities'

.get(
'/items/:itemId',
async ({ params: { itemId }, query: { instance = 'wikidata' }, wikibase }) => {
async ({ params: { itemId }, query: { instance = 'wikidata' }, wikibase, status }) => {
const item = await wikibase.getItem(instance, itemId)
if (!item) {
return ApiErrorHandler.notFoundError(`Item ${itemId} not found`)
return status(404, ApiErrorHandler.notFoundError('Item', itemId))
}
return { data: item }
},
Expand Down
14 changes: 10 additions & 4 deletions backend/src/services/wikibase.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class WikibaseService {
/**
* Get property details by ID
*/
async getProperty(instanceId: string, propertyId: PropertyId): Promise<PropertyDetails> {
async getProperty(instanceId: string, propertyId: PropertyId): Promise<PropertyDetails | null> {
const client = this.getClient(instanceId)

const params = {
Expand All @@ -166,12 +166,15 @@ export class WikibaseService {
const response = (await client.get(params)) as WikibaseGetEntitiesResponse

if (response.error) {
if (response.error.code === 'missingentity' || response.error.code === 'no-such-entity') {
return null
}
throw new Error(`Failed to get property: ${response.error.info}`)
}

const entity = response.entities?.[propertyId]
if (!entity || entity.missing !== undefined) {
throw new Error(`Property ${propertyId} not found`)
return null
}

const labels = entity.labels ?? {}
Expand Down Expand Up @@ -263,7 +266,7 @@ export class WikibaseService {
/**
* Get item details by ID
*/
async getItem(instanceId: string, itemId: ItemId): Promise<ItemDetails> {
async getItem(instanceId: string, itemId: ItemId): Promise<ItemDetails | null> {
const client = this.getClient(instanceId)

const params = {
Expand All @@ -276,12 +279,15 @@ export class WikibaseService {
const response = (await client.get(params)) as WikibaseGetEntitiesResponse

if (response.error) {
if (response.error.code === 'missingentity' || response.error.code === 'no-such-entity') {
return null
}
throw new Error(`Failed to get item: ${response.error.info}`)
}

const entity = response.entities?.[itemId]
if (!entity || entity.missing !== undefined) {
throw new Error(`Item ${itemId} not found`)
return null
}

const itemDetails: ItemDetails = {
Expand Down
120 changes: 120 additions & 0 deletions backend/tests/api/wikibase/entities.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { wikibaseEntitiesApi } from '@backend/api/wikibase/entities'
import { wikibaseService } from '@backend/services/wikibase.service'
import { treaty } from '@elysiajs/eden'
import { beforeEach, describe, expect, test } from 'bun:test'
import Elysia from 'elysia'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify Elysia import style

Confirm that a default import from 'elysia' is valid in your setup. If not, switch to a named import to match the rest of the codebase.

Run to check consistency:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 "import\s+Elysia\s+from\s+'elysia'|import\s+\{\s*Elysia\s*\}\s+from\s+'elysia'" --type=ts --type=tsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for both default and named Elysia imports without relying on file-type filters
rg -nP -C2 "import\s+(?:Elysia|\{\s*Elysia\s*\})\s+from\s+'elysia'" -g '*.ts' -g '*.tsx'

Length of output: 6361


Use named Elysia import for consistency

  • In backend/tests/api/wikibase/entities.test.ts (line 5), replace
    import Elysia from 'elysia'
    with
    import { Elysia } from 'elysia'
  • In backend/tests/api/wikibase/constraints.test.ts (line 5), replace
    import Elysia from 'elysia'
    with
    import { Elysia } from 'elysia'
🤖 Prompt for AI Agents
In backend/tests/api/wikibase/entities.test.ts (line 5) and
backend/tests/api/wikibase/constraints.test.ts (line 5), the module is imported
as a default export; change each to use the named Elysia import from 'elysia'
(i.e., replace the default import with a named import) so imports are consistent
across tests, then run lint/tests to ensure no import-related failures.


// Mock the wikibase service
const mockWikibaseService = {
getProperty: async () => {
return null // Simulate not found
},
getItem: async () => {
return null // Simulate not found
},
}

// Mock the service
Object.assign(wikibaseService, mockWikibaseService)

Comment on lines +17 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Restore wikibaseService after tests to avoid cross-suite pollution

Overriding the singleton globally can leak into other tests. Save originals and restore them after the suite.

Apply:

@@
-import { beforeEach, describe, expect, test } from 'bun:test'
+import { beforeEach, afterAll, describe, expect, test } from 'bun:test'
@@
-// Mock the service
-Object.assign(wikibaseService, mockWikibaseService)
+// Preserve originals and mock the service
+const originalWikibaseService = {
+  getProperty: wikibaseService.getProperty.bind(wikibaseService),
+  getItem: wikibaseService.getItem.bind(wikibaseService),
+}
+Object.assign(wikibaseService, mockWikibaseService)
@@
 describe('Wikibase Entities API', () => {
   let api: ReturnType<typeof createTestApi>
@@
   beforeEach(() => {
     api = createTestApi()
   })
+
+  afterAll(() => {
+    Object.assign(wikibaseService, originalWikibaseService)
+  })

Also applies to: 48-50

🤖 Prompt for AI Agents
In backend/tests/api/wikibase/entities.test.ts around lines 38-40 (and also
48-50), the test suite overwrites the singleton wikibaseService directly which
can leak into other test suites; modify the file to save the original service
reference before assigning the mock (e.g., const originalWikibaseService = {
...wikibaseService } or const originalWikibaseService = wikibaseService), then
assign the mock for the suite, and restore the original in an afterAll (or
afterEach) hook by reassigning the saved reference back to wikibaseService so
the global singleton is returned to its original state after tests complete.

const createTestApi = () => {
return treaty(new Elysia().use(wikibaseEntitiesApi)).api
}

describe('Wikibase Entities API', () => {
let api: ReturnType<typeof createTestApi>

beforeEach(() => {
api = createTestApi()
})

describe('Property endpoints', () => {
test('should return 404 for non-existent property', async () => {
const { data, status, error } = await api.wikibase.entities
.properties({ propertyId: 'P1000000' })
.get({
query: { instance: 'wikidata' },
})

expect(data).toBeNull()
expect(status).toBe(404)
expect(error).toHaveProperty('value', {
data: [],
errors: [
{
details: [],
code: 'NOT_FOUND',
message: "Property with identifier 'P1000000' not found",
},
],
})
})
})

describe('Item endpoints', () => {
test('should return 404 for non-existent item', async () => {
const { data, status, error } = await api.wikibase.entities
.items({ itemId: 'Q1000000' })
.get({
query: { instance: 'wikidata' },
})

expect(data).toBeNull()
expect(status).toBe(404)
expect(error).toHaveProperty('value', {
data: [],
errors: [
{
details: [],
code: 'NOT_FOUND',
message: "Item with identifier 'Q1000000' not found",
},
],
})
})

test('should return 404 when wikibase.getItem throws an error for not found', async () => {
const { data, status, error } = await api.wikibase.entities
.items({ itemId: 'Q9999999' })
.get({
query: { instance: 'wikidata' },
})

expect(data).toBeNull()
expect(status).toBe(404)
expect(error).toHaveProperty('value', {
data: [],
errors: [
{
details: [],
code: 'NOT_FOUND',
message: "Item with identifier 'Q9999999' not found",
},
],
})
})
})

describe('Item endpoints', () => {
test('should return 404 for non-existent item', async () => {
const { data, status, error } = await api.wikibase.entities
.items({ itemId: 'Q1000000' })
.get({
query: { instance: 'wikidata' },
})

expect(data).toBeNull()
expect(status).toBe(404)
expect(error).toHaveProperty('value', {
data: [],
errors: [
{
details: [],
code: 'NOT_FOUND',
message: "Item with identifier 'Q1000000' not found",
},
],
})
})
})
})