Skip to content

Commit 2c6bf16

Browse files
cgokeyChristopher Gokey
andauthored
KMS-516: Fixed update to delete first, then update. (#13)
* KMS-516: Fixed update to delete the record first. --------- Co-authored-by: Christopher Gokey <[email protected]>
1 parent 4687368 commit 2c6bf16

File tree

14 files changed

+922
-218
lines changed

14 files changed

+922
-218
lines changed

serverless-configs/aws-functions.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ createConcept:
3737
- http:
3838
method: post
3939
cors: ${file(./serverless-configs/${self:provider.name}-cors-configuration.yml)}
40-
path: concept/{conceptId}
40+
path: concept
4141
createConcepts:
4242
handler: serverless/src/createConcepts/handler.default
4343
timeout: ${env:LAMBDA_TIMEOUT, '30'}
@@ -53,7 +53,7 @@ updateConcept:
5353
- http:
5454
method: put
5555
cors: ${file(./serverless-configs/${self:provider.name}-cors-configuration.yml)}
56-
path: concept/{conceptId}
56+
path: concept
5757

5858
deleteConcept:
5959
handler: serverless/src/deleteConcept/handler.default

serverless/src/createConcept/__tests__/handler.test.js

Lines changed: 100 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,90 +6,165 @@ import {
66
} from 'vitest'
77
import createConcept from '../handler'
88
import conceptIdExists from '../../utils/conceptIdExists'
9+
import getConceptId from '../../utils/getConceptId'
910
import { getApplicationConfig } from '../../utils/getConfig'
1011
import { sparqlRequest } from '../../utils/sparqlRequest'
1112

1213
// Mock the dependencies
1314
vi.mock('../../utils/conceptIdExists')
15+
vi.mock('../../utils/getConceptId')
1416
vi.mock('../../utils/getConfig')
1517
vi.mock('../../utils/sparqlRequest')
1618

1719
describe('createConcept', () => {
18-
const mockEvent = {
19-
body: '<rdf:RDF>...</rdf:RDF>',
20-
pathParameters: { conceptId: '123' }
21-
}
22-
20+
const mockRdfXml = '<rdf:RDF>...</rdf:RDF>'
21+
const mockEvent = { body: mockRdfXml }
2322
const mockDefaultHeaders = { 'Content-Type': 'application/json' }
24-
beforeAll(() => {
25-
vi.spyOn(console, 'error').mockImplementation(() => {})
26-
vi.spyOn(console, 'log').mockImplementation(() => {})
27-
})
23+
const mockConceptId = '123'
24+
const mockConceptIRI = `https://gcmd.earthdata.nasa.gov/kms/concept/${mockConceptId}`
2825

2926
beforeEach(() => {
3027
vi.resetAllMocks()
3128
getApplicationConfig.mockReturnValue({ defaultResponseHeaders: mockDefaultHeaders })
29+
getConceptId.mockReturnValue(mockConceptId)
30+
vi.spyOn(console, 'log').mockImplementation(() => {})
31+
vi.spyOn(console, 'error').mockImplementation(() => {})
3232
})
3333

34-
test('should return 404 if concept already exists', async () => {
35-
conceptIdExists.mockResolvedValue(true)
34+
test('should handle missing body in event', async () => {
35+
const eventWithoutBody = {}
3636

37-
const result = await createConcept(mockEvent)
37+
const result = await createConcept(eventWithoutBody)
3838

3939
expect(result).toEqual({
40-
statusCode: 404,
41-
body: JSON.stringify({ message: 'Concept https://gcmd.earthdata.nasa.gov/kms/concept/123 already exists.' }),
40+
statusCode: 400,
41+
body: JSON.stringify({
42+
message: 'Error creating concept',
43+
error: 'Missing RDF/XML data in request body'
44+
}),
4245
headers: mockDefaultHeaders
4346
})
4447
})
4548

46-
test('should create concept and return 200 if concept does not exist', async () => {
49+
test('should successfully create a concept', async () => {
4750
conceptIdExists.mockResolvedValue(false)
4851
sparqlRequest.mockResolvedValue({ ok: true })
4952

5053
const result = await createConcept(mockEvent)
5154

55+
expect(getConceptId).toHaveBeenCalledWith(mockRdfXml)
56+
expect(conceptIdExists).toHaveBeenCalledWith(mockConceptIRI)
5257
expect(sparqlRequest).toHaveBeenCalledWith({
5358
contentType: 'application/rdf+xml',
5459
accept: 'application/rdf+xml',
5560
path: '/statements',
5661
method: 'POST',
57-
body: mockEvent.body
62+
body: mockRdfXml
63+
})
64+
65+
expect(result).toEqual({
66+
statusCode: 201,
67+
body: JSON.stringify({
68+
message: 'Successfully created concept',
69+
conceptId: mockConceptId
70+
}),
71+
headers: mockDefaultHeaders
5872
})
73+
})
74+
75+
test('should return 409 if concept already exists', async () => {
76+
conceptIdExists.mockResolvedValue(true)
77+
78+
const result = await createConcept(mockEvent)
5979

6080
expect(result).toEqual({
61-
statusCode: 200,
62-
body: 'Successfully loaded RDF XML into RDF4J',
81+
statusCode: 409,
82+
body: JSON.stringify({ message: `Concept ${mockConceptIRI} already exists.` }),
6383
headers: mockDefaultHeaders
6484
})
6585
})
6686

67-
test('should return 500 if sparqlRequest fails', async () => {
87+
test('should handle getConceptId throwing an error', async () => {
88+
getConceptId.mockImplementation(() => {
89+
throw new Error('Invalid XML')
90+
})
91+
92+
const result = await createConcept(mockEvent)
93+
94+
expect(result).toEqual({
95+
statusCode: 400,
96+
body: JSON.stringify({
97+
message: 'Error creating concept',
98+
error: 'Invalid XML'
99+
}),
100+
headers: mockDefaultHeaders
101+
})
102+
})
103+
104+
test('should handle missing concept ID', async () => {
105+
getConceptId.mockReturnValue(null)
106+
107+
const result = await createConcept(mockEvent)
108+
109+
expect(result).toEqual({
110+
statusCode: 400,
111+
body: JSON.stringify({
112+
message: 'Error creating concept',
113+
error: 'Invalid or missing concept ID'
114+
}),
115+
headers: mockDefaultHeaders
116+
})
117+
})
118+
119+
test('should handle sparqlRequest failure', async () => {
68120
conceptIdExists.mockResolvedValue(false)
69121
sparqlRequest.mockResolvedValue({
70122
ok: false,
71123
status: 500,
72-
text: () => Promise.resolve('Server error')
124+
text: async () => 'Internal Server Error'
125+
})
126+
127+
const result = await createConcept(mockEvent)
128+
129+
expect(result).toEqual({
130+
statusCode: 400,
131+
body: JSON.stringify({
132+
message: 'Error creating concept',
133+
error: 'HTTP error! status: 500'
134+
}),
135+
headers: mockDefaultHeaders
73136
})
74137

138+
expect(console.log).toHaveBeenCalledWith('Response text:', 'Internal Server Error')
139+
})
140+
141+
test('should handle conceptIdExists throwing an error', async () => {
142+
conceptIdExists.mockRejectedValue(new Error('Database error'))
143+
75144
const result = await createConcept(mockEvent)
76145

77146
expect(result).toEqual({
78-
statusCode: 500,
79-
body: 'Error loading RDF XML into RDF4J',
147+
statusCode: 400,
148+
body: JSON.stringify({
149+
message: 'Error creating concept',
150+
error: 'Database error'
151+
}),
80152
headers: mockDefaultHeaders
81153
})
82154
})
83155

84-
test('should return 500 if an error is thrown', async () => {
156+
test('should handle sparqlRequest throwing an error', async () => {
85157
conceptIdExists.mockResolvedValue(false)
86158
sparqlRequest.mockRejectedValue(new Error('Network error'))
87159

88160
const result = await createConcept(mockEvent)
89161

90162
expect(result).toEqual({
91-
statusCode: 500,
92-
body: 'Error loading RDF XML into RDF4J',
163+
statusCode: 400,
164+
body: JSON.stringify({
165+
message: 'Error creating concept',
166+
error: 'Network error'
167+
}),
93168
headers: mockDefaultHeaders
94169
})
95170
})

serverless/src/createConcept/handler.js

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import conceptIdExists from '../utils/conceptIdExists'
2+
import getConceptId from '../utils/getConceptId'
23
import { getApplicationConfig } from '../utils/getConfig'
34
import { sparqlRequest } from '../utils/sparqlRequest'
45

@@ -12,8 +13,6 @@ import { sparqlRequest } from '../utils/sparqlRequest'
1213
* @function createConcept
1314
* @param {Object} event - The Lambda event object.
1415
* @param {string} event.body - The RDF/XML representation of the concept to be created.
15-
* @param {Object} event.pathParameters - The path parameters from the API Gateway event.
16-
* @param {string} event.pathParameters.conceptId - The ID of the concept to be created.
1716
* @returns {Promise<Object>} A promise that resolves to an object containing the statusCode, body, and headers.
1817
*
1918
* @example
@@ -34,22 +33,29 @@ import { sparqlRequest } from '../utils/sparqlRequest'
3433
*/
3534
const createConcept = async (event) => {
3635
const { defaultResponseHeaders } = getApplicationConfig()
37-
const { body: rdfXml } = event
38-
const { conceptId } = event.pathParameters // Assuming the concept ID is passed as a path parameter
36+
const { body: rdfXml } = event || {} // Use empty object as fallback
3937

40-
// Create the basic auth header
41-
const conceptIRI = `https://gcmd.earthdata.nasa.gov/kms/concept/${conceptId}`
38+
try {
39+
if (!rdfXml) {
40+
throw new Error('Missing RDF/XML data in request body')
41+
}
4242

43-
const exists = await conceptIdExists(conceptIRI)
44-
if (exists) {
45-
return {
46-
statusCode: 404,
47-
body: JSON.stringify({ message: `Concept ${conceptIRI} already exists.` }),
48-
headers: defaultResponseHeaders
43+
const conceptId = getConceptId(rdfXml)
44+
if (!conceptId) {
45+
throw new Error('Invalid or missing concept ID')
46+
}
47+
48+
const conceptIRI = `https://gcmd.earthdata.nasa.gov/kms/concept/${conceptId}`
49+
50+
const exists = await conceptIdExists(conceptIRI)
51+
if (exists) {
52+
return {
53+
statusCode: 409,
54+
body: JSON.stringify({ message: `Concept ${conceptIRI} already exists.` }),
55+
headers: defaultResponseHeaders
56+
}
4957
}
50-
}
5158

52-
try {
5359
const response = await sparqlRequest({
5460
contentType: 'application/rdf+xml',
5561
accept: 'application/rdf+xml',
@@ -67,16 +73,22 @@ const createConcept = async (event) => {
6773
console.log('Successfully loaded RDF XML into RDF4J')
6874

6975
return {
70-
statusCode: 200,
71-
body: 'Successfully loaded RDF XML into RDF4J',
76+
statusCode: 201, // Changed from 200 to 201 Created
77+
body: JSON.stringify({
78+
message: 'Successfully created concept',
79+
conceptId
80+
}),
7281
headers: defaultResponseHeaders
7382
}
7483
} catch (error) {
75-
console.error('Error loading RDF XML into RDF4J:', error)
84+
console.error('Error creating concept:', error)
7685

7786
return {
78-
statusCode: 500,
79-
body: 'Error loading RDF XML into RDF4J',
87+
statusCode: 400, // Changed from 500 to 400 for client errors
88+
body: JSON.stringify({
89+
message: 'Error creating concept',
90+
error: error.message
91+
}),
8092
headers: defaultResponseHeaders
8193
}
8294
}

0 commit comments

Comments
 (0)