Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KMS-516: Fixed update to delete first, then update. #13

Merged
merged 5 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
136 changes: 35 additions & 101 deletions serverless/src/deleteConcept/__tests__/handler.test.js
Original file line number Diff line number Diff line change
@@ -1,169 +1,103 @@
import {
describe,
test,
expect,
vi,
beforeEach,
afterEach
beforeEach
} from 'vitest'
import deleteConcept from '../handler'
import deleteTriples from '../../utils/deleteTriples'
import { getApplicationConfig } from '../../utils/getConfig'
import { sparqlRequest } from '../../utils/sparqlRequest'

// Mock the dependencies
vi.mock('../../utils/deleteTriples')
vi.mock('../../utils/getConfig')
vi.mock('../../utils/sparqlRequest')

describe('deleteConcept', () => {
const mockDefaultHeaders = { 'X-Custom-Header': 'value' }
const mockConceptId = '123'
const mockDefaultHeaders = { 'Content-Type': 'application/json' }
const mockEvent = {
pathParameters: { conceptId: mockConceptId }
pathParameters: { conceptId: '123' }
}
let consoleLogSpy
let consoleErrorSpy

beforeEach(() => {
vi.resetAllMocks()
getApplicationConfig.mockReturnValue({ defaultResponseHeaders: mockDefaultHeaders })

// Set up spies for console.log and console.error
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
})
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'log').mockImplementation(() => {})

afterEach(() => {
// Restore the original console methods after each test
consoleLogSpy.mockRestore()
consoleErrorSpy.mockRestore()
getApplicationConfig.mockReturnValue({ defaultResponseHeaders: mockDefaultHeaders })
})

test('should successfully delete a concept and return 200', async () => {
sparqlRequest.mockResolvedValue({ ok: true })
test('should successfully delete a concept', async () => {
deleteTriples.mockResolvedValue({ deleteResponse: { ok: true } })

const result = await deleteConcept(mockEvent)

expect(sparqlRequest).toHaveBeenCalledWith({
contentType: 'application/sparql-update',
accept: 'application/sparql-results+json',
path: '/statements',
method: 'POST',
body: expect.stringContaining(`https://gcmd.earthdata.nasa.gov/kms/concept/${mockConceptId}`)
})

expect(deleteTriples).toHaveBeenCalledWith('https://gcmd.earthdata.nasa.gov/kms/concept/123')
expect(result).toEqual({
statusCode: 200,
body: JSON.stringify({ message: `Successfully deleted concept: ${mockConceptId}` }),
body: JSON.stringify({ message: 'Successfully deleted concept: 123' }),
headers: mockDefaultHeaders
})

expect(consoleLogSpy).toHaveBeenCalledWith(`Successfully deleted concept: ${mockConceptId}`)
})

test('should handle SPARQL endpoint errors and return 500', async () => {
const errorMessage = 'SPARQL endpoint error'
sparqlRequest.mockResolvedValue({
ok: false,
status: 400,
text: () => Promise.resolve(errorMessage)
})
test('should return 500 if deleteTriples fails', async () => {
const mockError = new Error('Delete failed')
deleteTriples.mockRejectedValue(mockError)

const result = await deleteConcept(mockEvent)

expect(sparqlRequest).toHaveBeenCalled()
expect(result).toEqual({
statusCode: 500,
body: JSON.stringify({
message: 'Error deleting concept',
error: 'HTTP error! status: 400'
error: 'Delete failed'
}),
headers: mockDefaultHeaders
})

expect(consoleLogSpy).toHaveBeenCalledWith('Response text:', errorMessage)
expect(consoleErrorSpy).toHaveBeenCalledWith('Error deleting concept:', expect.any(Error))
})

test('should handle unexpected errors and return 500', async () => {
const error = new Error('Unexpected error')
sparqlRequest.mockRejectedValue(error)
test('should return 500 if deleteTriples returns non-ok response', async () => {
deleteTriples.mockResolvedValue({
deleteResponse: {
ok: false,
status: 400,
text: () => Promise.resolve('Bad Request')
}
})

const result = await deleteConcept(mockEvent)

expect(sparqlRequest).toHaveBeenCalled()
expect(result).toEqual({
statusCode: 500,
body: JSON.stringify({
message: 'Error deleting concept',
error: 'Unexpected error'
error: 'HTTP error! status: 400'
}),
headers: mockDefaultHeaders
})

expect(consoleErrorSpy).toHaveBeenCalledWith('Error deleting concept:', error)
})

test('should construct the correct SPARQL query', async () => {
sparqlRequest.mockResolvedValue({ ok: true })

await deleteConcept(mockEvent)

const expectedQuery = `
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
DELETE {
?s ?p ?o .
}
WHERE {
?s ?p ?o .
FILTER(?s = <https://gcmd.earthdata.nasa.gov/kms/concept/${mockConceptId}>)
}
`

expect(sparqlRequest).toHaveBeenCalledWith({
accept: 'application/sparql-results+json',
body: expectedQuery,
contentType: 'application/sparql-update',
method: 'POST',
path: '/statements'
})
})

test('should handle missing conceptId in path parameters', async () => {
test('should handle missing conceptId', async () => {
const eventWithoutConceptId = { pathParameters: {} }

const result = await deleteConcept(eventWithoutConceptId)

expect(result).toEqual({
expect(result).toMatchObject({
statusCode: 500,
body: expect.stringContaining('Error deleting concept'),
headers: mockDefaultHeaders
})

expect(consoleErrorSpy).toHaveBeenCalled()
})

test('should use the correct content type and accept headers', async () => {
sparqlRequest.mockResolvedValue({ ok: true })

await deleteConcept(mockEvent)

expect(sparqlRequest).toHaveBeenCalledWith(
expect.objectContaining({
contentType: 'application/sparql-update',
accept: 'application/sparql-results+json'
})
)
const body = JSON.parse(result.body)
expect(body).toHaveProperty('message', 'Error deleting concept')
expect(body).toHaveProperty('error')
expect(typeof body.error).toBe('string')
})

test('should use the correct path and method for the SPARQL request', async () => {
sparqlRequest.mockResolvedValue({ ok: true })
test('should use correct conceptIRI format', async () => {
deleteTriples.mockResolvedValue({ deleteResponse: { ok: true } })

await deleteConcept(mockEvent)

expect(sparqlRequest).toHaveBeenCalledWith(
expect.objectContaining({
path: '/statements',
method: 'POST'
})
)
expect(deleteTriples).toHaveBeenCalledWith('https://gcmd.earthdata.nasa.gov/kms/concept/123')
})
})
25 changes: 3 additions & 22 deletions serverless/src/deleteConcept/handler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import deleteTriples from '../utils/deleteTriples'
import { getApplicationConfig } from '../utils/getConfig'
import { sparqlRequest } from '../utils/sparqlRequest'

/**
* Deletes a SKOS Concept from the RDF store based on its rdf:about identifier.
Expand Down Expand Up @@ -37,35 +37,16 @@ const deleteConcept = async (event) => {
// Construct the full IRI
const conceptIRI = `https://gcmd.earthdata.nasa.gov/kms/concept/${conceptId}`

// Construct the SPARQL DELETE query
const deleteQuery = `
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
DELETE {
?s ?p ?o .
}
WHERE {
?s ?p ?o .
FILTER(?s = <${conceptIRI}>)
}
`

try {
const response = await sparqlRequest({
contentType: 'application/sparql-update',
accept: 'application/sparql-results+json',
path: '/statements',
method: 'POST',
body: deleteQuery
})
const { deleteResponse: response } = await deleteTriples(conceptIRI)

if (!response.ok) {
const responseText = await response.text()
console.log('Response text:', responseText)
throw new Error(`HTTP error! status: ${response.status}`)
}

console.log(`Successfully deleted concept: ${conceptId}`)

// Return success response
return {
statusCode: 200,
body: JSON.stringify({ message: `Successfully deleted concept: ${conceptId}` }),
Expand Down
Loading