Skip to content

Commit

Permalink
feat(productImport): Set state to New for partner products if state i…
Browse files Browse the repository at this point in the history
…s not specified

Set state to New for REWE Digital Marketplace partner products if state is not specified.
Also, refactor timeout in integration.spec.coffee into a common constant, and increase its value.

Closes: #125
  • Loading branch information
Michael Grant committed Jul 6, 2018
1 parent 173b3b8 commit a42e4fb
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 41 deletions.
95 changes: 72 additions & 23 deletions lib/product-import.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,20 @@ class ProductImport

_prepareUpdateProduct: (productToProcess, existingProduct) ->
productToProcess = @_ensureDefaults(productToProcess)
if (productToProcess.state)
stateRef = productToProcess.state
statePredicate = "id=\"#{productToProcess.state.id}\" and type=\"ProductState\""
else
stateRef = { id: existingProduct.state.id }
statePredicate = "id=\"#{existingProduct.state.id}\" and type=\"ProductState\""

Promise.all [
@_resolveProductCategories(productToProcess.categories)
@_resolveReference(@client.taxCategories, 'taxCategory', productToProcess.taxCategory, "name=\"#{productToProcess.taxCategory?.id}\"")
@_resolveReference(@client.states, 'state', stateRef, statePredicate)
@_fetchAndResolveCustomReferences(productToProcess)
]
.spread (prodCatsIds, taxCatId) =>
.spread (prodCatsIds, taxCatId, stateId) =>
if taxCatId
productToProcess.taxCategory =
id: taxCatId
Expand All @@ -462,6 +470,9 @@ class ProductImport
productToProcess.categories = _.map prodCatsIds, (catId) ->
id: catId
typeId: 'category'
productToProcess.state =
id: stateId
typeId: 'state'
productToProcess.slug = @_updateProductSlug productToProcess, existingProduct
Promise.resolve productToProcess

Expand All @@ -477,13 +488,21 @@ class ProductImport

_prepareNewProduct: (product) ->
product = @_ensureDefaults(product)
if (product.state)
stateRef = product.state
statePredicate = "id=\"#{product.state.id}\" and type=\"ProductState\""
else
stateRef = ''
statePredicate = 'key="New" and type="ProductState"'

Promise.all [
@_resolveReference(@client.productTypes, 'productType', product.productType, "name=\"#{product.productType?.id}\"")
@_resolveProductCategories(product.categories)
@_resolveReference(@client.taxCategories, 'taxCategory', product.taxCategory, "name=\"#{product.taxCategory?.id}\"")
@_resolveReference(@client.states, 'state', stateRef, statePredicate)
@_fetchAndResolveCustomReferences(product)
]
.spread (prodTypeId, prodCatsIds, taxCatId) =>
.spread (prodTypeId, prodCatsIds, taxCatId, stateId) =>
if prodTypeId
product.productType =
id: prodTypeId
Expand All @@ -500,6 +519,9 @@ class ProductImport
if product.name
#Promise.reject 'Product name is required.'
product.slug = @_generateSlug product.name
product.state =
id: stateId
typeId: 'state'
Promise.resolve product


Expand Down Expand Up @@ -603,25 +625,52 @@ class ProductImport
_resolveReference: (service, refKey, ref, predicate) ->
new Promise (resolve, reject) =>
if not ref
resolve()
if not @_cache[refKey]
@_cache[refKey] = {}
if @_cache[refKey][ref.id]
resolve(@_cache[refKey][ref.id].id)
else
request = service.where(predicate)
if refKey is 'product'
request.staged(true)
request.fetch()
.then (result) =>
if result.body.count is 0
reject "Didn't find any match while resolving #{refKey} (#{predicate})"
if (refKey is 'state') # References are needed to states even if the incoming product doesn't itself (yet) have one.
if not @_cache[refKey]
@_cache[refKey] = {}
if @_cache[refKey][predicate]
if (typeof @_cache[refKey][predicate] is 'object' and @_cache[refKey][predicate].constructor.name is 'Promise')
@_cache[refKey][predicate].then (result) =>
@_processCompletedStateRequest(refKey, predicate, result, resolve, reject)
else
resolve(@_cache[refKey][predicate].id)
else
if _.size(result.body.results) > 1
@logger.warn "Found more than 1 #{refKey} for #{ref.id}"
@_cache[refKey][ref.id] = result.body.results[0]
if refKey is 'productType'
@_cache[refKey][result.body.results[0].id] = result.body.results[0]
resolve(result.body.results[0].id)

module.exports = ProductImport
request = service.where(predicate)
fetch = request.fetch()
@_cache[refKey][predicate] = fetch
fetch.then (result) =>
@_processCompletedStateRequest(refKey, predicate, result, resolve, reject)
else
resolve()
else
if not @_cache[refKey]
@_cache[refKey] = {}
if @_cache[refKey][ref.id]
resolve(@_cache[refKey][ref.id].id)
else
request = service.where(predicate)
if refKey is 'product'
request.staged(true)
request.fetch()
.then (result) =>
if result.body.count is 0
reject "Didn't find any match while resolving #{refKey} (#{predicate})"
else
if _.size(result.body.results) > 1
@logger.warn "Found more than 1 #{refKey} for #{ref.id}"
@_cache[refKey][ref.id] = result.body.results[0]
if refKey is 'productType'
@_cache[refKey][result.body.results[0].id] = result.body.results[0]
resolve(result.body.results[0].id)

_processCompletedStateRequest: (refKey, predicate, result, resolve, reject) =>
if result.body.count is 0
reject "Didn't find any match while resolving #{refKey} (#{predicate})"
else
if _.size(result.body.results) > 1
@logger.warn "Found more than 1 #{refKey} for #{predicate}"
@_cache[refKey][result.body.results[0].id] = result.body.results[0] # Cache by ID for future calls where it's known
@_cache[refKey][predicate] = result.body.results[0] # Cache by predicate for where (as here) it's not
resolve(result.body.results[0].id)

module.exports = ProductImport
21 changes: 12 additions & 9 deletions test/integration.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ sampleTaxCategory = require '../samples/sample-tax-category.json'

frozenTimeStamp = new Date().getTime()

TEST_TIMEOUT = 15000

ensureResource = (service, predicate, sampleData) ->
debug 'Ensuring existence for: %s', predicate
service.where(predicate).fetch()
Expand Down Expand Up @@ -66,14 +68,14 @@ describe 'Product import integration tests', ->
.then ->
done()
.catch (err) -> done(_.prettify err)
, 10000 # 10sec
, TEST_TIMEOUT

afterEach (done) ->
@logger.info 'About to cleanup...'
deleteProducts(@logger, @client)
.then -> done()
.catch (err) -> done(_.prettify err)
, 10000 # 10sec
, TEST_TIMEOUT


it 'should import two new products', (done) ->
Expand All @@ -99,7 +101,7 @@ describe 'Product import integration tests', ->
expect(fetchedProduct[0].slug).toEqual sampleImport.products[0].slug
done()
.catch done
, 10000
, TEST_TIMEOUT

it 'should do nothing for empty products list', (done) ->
@import._processBatches([])
Expand All @@ -108,7 +110,7 @@ describe 'Product import integration tests', ->
expect(@import._summary.updated).toBe 0
done()
.catch done
, 10000
, TEST_TIMEOUT

it 'should generate missing slug', (done) ->
sampleImport = _.deepClone(sampleImportJson)
Expand All @@ -127,7 +129,7 @@ describe 'Product import integration tests', ->
expect(fetchedProduct[0].slug.en).toBe "product-sync-test-product-1-#{frozenTimeStamp}"
done()
.catch done
, 10000
, TEST_TIMEOUT

it 'should update existing product', (done) ->
sampleImport = _.deepClone(sampleImportJson)
Expand Down Expand Up @@ -165,7 +167,7 @@ describe 'Product import integration tests', ->
expect(_.size result.body.results[0].variants).toBe 2
done()
.catch (err) -> done(_.prettify err.body)
, 10000
, TEST_TIMEOUT

it 'should continue on error - duplicate slug', (done) ->
# FIXME: looks like the API doesn't correctly validate for duplicate slugs
Expand Down Expand Up @@ -206,7 +208,7 @@ describe 'Product import integration tests', ->
expect(@import._summary.created).toBe 1
done()
.catch done
, 10000
, TEST_TIMEOUT

it 'should handle set type attributes correctly', (done) ->
sampleImport = _.deepClone sampleImportJson
Expand Down Expand Up @@ -238,7 +240,7 @@ describe 'Product import integration tests', ->
expect(result.body.results[0].masterVariant.attributes[0].value).toEqual setTextAttributeUpdated.value
done()
.catch done
, 10000
, TEST_TIMEOUT

it 'should filter unknown attributes and import product without errors', (done) ->
sampleImport = _.deepClone sampleImportJson
Expand All @@ -255,7 +257,8 @@ describe 'Product import integration tests', ->
expect(@import._summary.unknownAttributeNames).toEqual ['unknownAttribute']
done()
.catch done
, 10000
, TEST_TIMEOUT


it 'should update/create product with a new enum key', (done) ->
@logger.info ':: should update/create product with a new enum key'
Expand Down
115 changes: 112 additions & 3 deletions test/integration/product-import.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ sampleProductTypeForProduct =

bigProductType =
name: 'bigProductType'
description: 'test big poroductType description'
description: 'test big productType description'
attributes: [1..1031].map (i) ->
name: "attr_#{i}"
label:
Expand Down Expand Up @@ -122,6 +122,21 @@ sampleCustomerGroup =
sampleChannel =
key: 'test-channel'

newState =
key: "New"
type: "ProductState"
initial: true

extantState =
key: "AlsoNewButIWantToCheckPopulatingWithANonDefaultState"
type: "ProductState"
initial: true

updatedState =
key: "Updated"
type: "ProductState"
initial: false

ensureResource = (service, predicate, sampleData) ->
debug 'Ensuring existence for: %s', predicate
service.where(predicate).fetch()
Expand Down Expand Up @@ -165,8 +180,10 @@ describe 'Product Importer integration tests', ->
.then => ensureResource(@client.productTypes, 'name="productTypeForProductImport"', sampleProductTypeForProduct)
.then (@productType) => ensureResource(@client.customerGroups, 'name="test-group"', sampleCustomerGroup)
.then (@customerGroup) => ensureResource(@client.channels, 'key="test-channel"', sampleChannel)
.then (@channel) =>
ensureResource(@client.categories, 'externalId="test-category"', sampleCategory)
.then (@channel) => ensureResource(@client.states, 'key="New"', newState)
.then (@newState) => ensureResource(@client.states, 'key="AlsoNewButIWantToCheckPopulatingWithANonDefaultState"', extantState)
.then (@extantState) => ensureResource(@client.states, 'key="Updated"', updatedState)
.then (@updatedState) => ensureResource(@client.categories, 'externalId="test-category"', sampleCategory)
.then (@category) =>
done()
.catch (err) ->
Expand Down Expand Up @@ -216,6 +233,98 @@ describe 'Product Importer integration tests', ->
expect(@import._summary.productsWithMissingSKU).toBe(1)
done()

it 'should assign New state to products without a state specified', (done) ->
productDraft = createProduct()[0]

@import.performStream([productDraft], -> {})
.then =>
@fetchProducts(@productType.id)
.then (result) =>
expect(result.body.results.length).toBe(1)
product = result.body.results[0]

expect(product.state.id).toBe(@newState.id)
expect(@import._cache["state"][product.state.id].key).toBe("New")
done()

it 'should assign the appropriate state to products with a state specified', (done) ->
productDraft = createProduct()[0]

productDraft.state = {
typeId: "state",
id: @extantState.id
}

@import.performStream([productDraft], -> {})
.then =>
@fetchProducts(@productType.id)
.then (result) =>
expect(result.body.results.length).toBe(1)
product = result.body.results[0]

expect(product.state.id).toBe(@extantState.id)
expect(@import._cache["state"][product.state.id].key).toBe("AlsoNewButIWantToCheckPopulatingWithANonDefaultState")
done()

it 'should process updates for products where the update does not specify the state', (done) ->
productDraft = _.deepClone(createProduct()[0])

@import.performStream([productDraft], -> {})
.then =>
@fetchProducts(@productType.id)
.then (result) =>
expect(result.body.results.length).toBe(1)
product = result.body.results[0]

expect(product.state.id).toBe(@newState.id)
expect(@import._cache["state"][product.state.id].key).toBe("New")

productDraft = createProduct()[0]
productDraft.name.en = "Updated name"

@import.performStream([productDraft], _.noop)
.then =>
@fetchProducts(@productType.id)
.then (result) =>
expect(result.body.results.length).toBe(1)
product = result.body.results[0]

expect(product.name.en).toBe("Updated name")
expect(product.state.id).toBe(@newState.id)
expect(@import._cache["state"][product.state.id].key).toBe("New")
done()

it 'should process updates for products where the update does specifies a state', (done) ->
productDraft = _.deepClone(createProduct()[0])

@import.performStream([productDraft], -> {})
.then =>
@fetchProducts(@productType.id)
.then (result) =>
expect(result.body.results.length).toBe(1)
product = result.body.results[0]

expect(product.state.id).toBe(@newState.id)
expect(@import._cache["state"][product.state.id].key).toBe("New")

productDraft = createProduct()[0]
productDraft.name.en = "Updated name"
productDraft.state = {
typeId: "state",
id: @updatedState.id
}
@import.performStream([productDraft], -> {})
.then =>
@fetchProducts(@productType.id)
.then (result) =>
expect(result.body.results.length).toBe(1)
product = result.body.results[0]

expect(product.name.en).toBe("Updated name")
expect(product.state.id).toBe(@updatedState.id)
expect(@import._cache["state"][product.state.id].key).toBe("Updated")
done()

it 'should skip and continue on category not found', (done) ->
productDrafts = createProduct()
@import.performStream(productDrafts, -> {})
Expand Down
Loading

0 comments on commit a42e4fb

Please sign in to comment.