diff --git a/media-store/db/schema.cds b/media-store/db/schema.cds index 6a88006a..18466e95 100644 --- a/media-store/db/schema.cds +++ b/media-store/db/schema.cds @@ -34,18 +34,24 @@ entity Albums { key ID : Integer; title : String(120); artist : Association to Artists; + tracks : Association to many Tracks + on tracks.album = $self; } entity Employees : Persone { - reportsTo : Association to Employees; - title : String(20); - birthDate : DateTime; - hireDate : DateTime; + reportsTo : Association to Employees; + title : String(20); + birthDate : DateTime; + hireDate : DateTime; + subordinates : Association to many Employees + on subordinates.reportsTo = $self; } entity Customers : Persone { company : String(80); supportRep : Association to Employees; + invoices : Association to many Invoices + on invoices.customer = $self; } entity Invoices { diff --git a/media-store/package.json b/media-store/package.json index f876c74b..35ad1b7e 100644 --- a/media-store/package.json +++ b/media-store/package.json @@ -14,8 +14,9 @@ }, "scripts": { "start": "npx cds run", - "deploy:mychinookdb": "cds deploy --to sqlite:mychinook.db", - "import:data": "node ./util/importData.js sqlite:chinook.db sqlite:mychinook.db ./db/schema.cds" + "deploy": "cds deploy --to sqlite:mychinook.db", + "import": "node ./util/importData.js sqlite:chinook.db sqlite:mychinook.db ./db/schema.cds", + "test": "mocha test/media-service.test.js --verbose --timeout 10000" }, "cds": { "requires": { diff --git a/media-store/test/data/media-service.mock.js b/media-store/test/data/media-service.mock.js new file mode 100644 index 00000000..11957e5a --- /dev/null +++ b/media-store/test/data/media-service.mock.js @@ -0,0 +1,369 @@ +const FIRST_EMPLOYEE = { + "@odata.context": "$metadata#Employees/$entity", + ID: 1, + lastName: "Adams", + firstName: "Andrew", + city: "Edmonton", + state: "AB", + address: "11120 Jasper Ave NW", + country: "Canada", + postalCode: "T5K 2N1", + phone: "+1 (780) 428-9482", + fax: "+1 (780) 428-3457", + email: "andrew@chinookcorp.com", + title: "General Manager", + birthDate: "1962-02-17T21:00:00Z", + hireDate: "2002-08-13T21:00:00Z", + reportsTo_ID: null, +}; + +const SECOND_EMPLOYEE_WITH_EXPANDED_FIELDS = { + "@odata.context": "$metadata#Employees(subordinates(),reportsTo())/$entity", + ID: 2, + lastName: "Edwards", + firstName: "Nancy", + city: "Calgary", + state: "AB", + address: "825 8 Ave SW", + country: "Canada", + postalCode: "T2P 2T3", + phone: "+1 (403) 262-3443", + fax: "+1 (403) 262-3322", + email: "nancy@chinookcorp.com", + title: "Sales Manager", + birthDate: "1958-12-07T21:00:00Z", + hireDate: "2002-04-30T21:00:00Z", + reportsTo_ID: 1, + reportsTo: { + ID: 1, + lastName: "Adams", + firstName: "Andrew", + city: "Edmonton", + state: "AB", + address: "11120 Jasper Ave NW", + country: "Canada", + postalCode: "T5K 2N1", + phone: "+1 (780) 428-9482", + fax: "+1 (780) 428-3457", + email: "andrew@chinookcorp.com", + title: "General Manager", + birthDate: "1962-02-17T21:00:00Z", + hireDate: "2002-08-13T21:00:00Z", + reportsTo_ID: null, + }, + subordinates: [ + { + ID: 3, + lastName: "Peacock", + firstName: "Jane", + city: "Calgary", + state: "AB", + address: "1111 6 Ave SW", + country: "Canada", + postalCode: "T2P 5M5", + phone: "+1 (403) 262-3443", + fax: "+1 (403) 262-6712", + email: "jane@chinookcorp.com", + title: "Sales Support Agent", + birthDate: "1973-08-28T21:00:00Z", + hireDate: "2002-03-31T21:00:00Z", + reportsTo_ID: 2, + }, + { + ID: 4, + lastName: "Park", + firstName: "Margaret", + city: "Calgary", + state: "AB", + address: "683 10 Street SW", + country: "Canada", + postalCode: "T2P 5G3", + phone: "+1 (403) 263-4423", + fax: "+1 (403) 263-4289", + email: "margaret@chinookcorp.com", + title: "Sales Support Agent", + birthDate: "1947-09-18T21:00:00Z", + hireDate: "2003-05-02T21:00:00Z", + reportsTo_ID: 2, + }, + { + ID: 5, + lastName: "Johnson", + firstName: "Steve", + city: "Calgary", + state: "AB", + address: "7727B 41 Ave", + country: "Canada", + postalCode: "T3B 1Y7", + phone: "1 (780) 836-9987", + fax: "1 (780) 836-9543", + email: "steve@chinookcorp.com", + title: "Sales Support Agent", + birthDate: "1965-03-02T21:00:00Z", + hireDate: "2003-10-16T21:00:00Z", + reportsTo_ID: 2, + }, + ], +}; + +const ALL_ALBUMS_WITH_TRACKS_BY_ARTIST = { + "@odata.context": "$metadata#Albums(tracks())", + value: [ + { + ID: 1, + title: "For Those About To Rock We Salute You", + artist_ID: 1, + tracks: [ + { + ID: 1, + name: "For Those About To Rock (We Salute You)", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 343719, + bytes: 11170334, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 6, + name: "Put The Finger On You", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 205662, + bytes: 6713451, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 7, + name: "Let's Get It Up", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 233926, + bytes: 7636561, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 8, + name: "Inject The Venom", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 210834, + bytes: 6852860, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 9, + name: "Snowballed", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 203102, + bytes: 6599424, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 10, + name: "Evil Walks", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 263497, + bytes: 8611245, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 11, + name: "C.O.D.", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 199836, + bytes: 6566314, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 12, + name: "Breaking The Rules", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 263288, + bytes: 8596840, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 13, + name: "Night Of The Long Knives", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 205688, + bytes: 6706347, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 14, + name: "Spellbound", + composer: "Angus Young, Malcolm Young, Brian Johnson", + milliseconds: 270863, + bytes: 8817038, + unitPrice: 0.99, + album_ID: 1, + mediaType_ID: 1, + genre_ID: 1, + }, + ], + }, + { + ID: 4, + title: "Let There Be Rock", + artist_ID: 1, + tracks: [ + { + ID: 15, + name: "Go Down", + composer: "AC/DC", + milliseconds: 331180, + bytes: 10847611, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 16, + name: "Dog Eat Dog", + composer: "AC/DC", + milliseconds: 215196, + bytes: 7032162, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 17, + name: "Let There Be Rock", + composer: "AC/DC", + milliseconds: 366654, + bytes: 12021261, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 18, + name: "Bad Boy Boogie", + composer: "AC/DC", + milliseconds: 267728, + bytes: 8776140, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 19, + name: "Problem Child", + composer: "AC/DC", + milliseconds: 325041, + bytes: 10617116, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 20, + name: "Overdose", + composer: "AC/DC", + milliseconds: 369319, + bytes: 12066294, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 21, + name: "Hell Ain't A Bad Place To Be", + composer: "AC/DC", + milliseconds: 254380, + bytes: 8331286, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + { + ID: 22, + name: "Whole Lotta Rosie", + composer: "AC/DC", + milliseconds: 323761, + bytes: 10547154, + unitPrice: 0.99, + album_ID: 4, + mediaType_ID: 1, + genre_ID: 1, + }, + ], + }, + ], +}; + +const CUSTOMER_WITH_THEIR_SUPPORT_REP = { + "@odata.context": "$metadata#Customers(supportRep())/$entity", + ID: 1, + lastName: "Gonçalves", + firstName: "Luís", + city: "São José dos Campos", + state: "SP", + address: "Av. Brigadeiro Faria Lima, 2170", + country: "Brazil", + postalCode: "12227-000", + phone: "+55 (12) 3923-5555", + fax: "+55 (12) 3923-5566", + email: "luisg@embraer.com.br", + company: "Embraer - Empresa Brasileira de Aeronáutica S.A.", + supportRep_ID: 3, + supportRep: { + ID: 3, + lastName: "Peacock", + firstName: "Jane", + city: "Calgary", + state: "AB", + address: "1111 6 Ave SW", + country: "Canada", + postalCode: "T2P 5M5", + phone: "+1 (403) 262-3443", + fax: "+1 (403) 262-6712", + email: "jane@chinookcorp.com", + title: "Sales Support Agent", + birthDate: "1973-08-28T21:00:00Z", + hireDate: "2002-03-31T21:00:00Z", + reportsTo_ID: 2, + }, +}; + +const EIGHTH_ALBUM_TRACKS_COUNT = 3290; + +module.exports = { + FIRST_EMPLOYEE, + SECOND_EMPLOYEE_WITH_EXPANDED_FIELDS, + ALL_ALBUMS_WITH_TRACKS_BY_ARTIST, + CUSTOMER_WITH_THEIR_SUPPORT_REP, + EIGHTH_ALBUM_TRACKS_COUNT, +}; diff --git a/media-store/test/media-service.test.js b/media-store/test/media-service.test.js new file mode 100644 index 00000000..e9a0432d --- /dev/null +++ b/media-store/test/media-service.test.js @@ -0,0 +1,67 @@ +const cds = require("../../test/cds"); +const { GET, expect } = cds.test("media-store").in(__dirname, "../../"); + +const { + FIRST_EMPLOYEE, + SECOND_EMPLOYEE_WITH_EXPANDED_FIELDS, + ALL_ALBUMS_WITH_TRACKS_BY_ARTIST, + CUSTOMER_WITH_THEIR_SUPPORT_REP, + EIGHTH_ALBUM_TRACKS_COUNT, +} = require("./data/media-service.mock"); + +describe("Media service", () => { + it("should bootstrap the services successfully", () => { + const { MediaService, db } = cds.services; + const { Employees } = MediaService.entities; + + expect(MediaService).not.to.be.undefined; + expect(db).not.to.be.undefined; + expect(Employees).not.to.be.undefined; + }); + + describe("Employees", () => { + it("should return employee (ID=1)", async () => { + const { data } = await GET("/media/Employees(1)"); + expect(data).to.eql(FIRST_EMPLOYEE); + }); + + it("should return employee (ID=2) with subordinates and whom he reports to", async () => { + const { data } = await GET( + `/media/Employees(2)?$expand=subordinates,reportsTo` + ); + expect(data).to.eql(SECOND_EMPLOYEE_WITH_EXPANDED_FIELDS); + }); + }); + + describe("Albums", () => { + it("should return all albums with tracks by artist", async () => { + const { data } = await GET( + `/media/Albums?$filter=artist_ID eq 1&$expand=tracks` + ); + expect(data).to.eql(ALL_ALBUMS_WITH_TRACKS_BY_ARTIST); + }); + }); + + describe("Customers", () => { + it("should return customer with their invoices", async () => { + const { data } = await GET(`/media/Customers(1)?$expand=supportRep`); + expect(data).to.eql(CUSTOMER_WITH_THEIR_SUPPORT_REP); + }); + }); + + describe("Playlists", () => { + it("should return playlist with their tracks", async () => { + const { data } = await GET(`/media/Customers(1)?$expand=supportRep`); + expect(data).to.eql(CUSTOMER_WITH_THEIR_SUPPORT_REP); + }); + }); + + describe("Tracks", () => { + it("should return the number of tracks by album", async () => { + const { data } = await GET( + `/media/PlaylistTrack/$count?$filter=playlist_ID eq 8` + ); + expect(data).to.eql(EIGHTH_ALBUM_TRACKS_COUNT); + }); + }); +}); diff --git a/media-store/test/media-store.test.js b/media-store/test/media-store.test.js deleted file mode 100644 index e69de29b..00000000 diff --git a/media-store/test/test.http b/media-store/test/test.http index 3b07165c..f803da15 100644 --- a/media-store/test/test.http +++ b/media-store/test/test.http @@ -15,17 +15,19 @@ GET {{media-store-service}}/Artists ### ------------------------------------------------------------------------ # Get Albums GET {{media-store-service}}/Albums -?$expand=artist&$top=10 +?$filter=artist_ID eq 1 +&$expand=tracks,artist + ### ------------------------------------------------------------------------ # Get Customers -GET {{media-store-service}}/Customers -?$expand=supportRep&$top=10 +GET {{media-store-service}}/Customers(1) +?$expand=supportRep ### ------------------------------------------------------------------------ # Get Employees -GET {{media-store-service}}/Employees -?$expand=reportsTo&$top=10 +GET {{media-store-service}}/Employees(1) +?$expand=subordinates,reportsTo ### ------------------------------------------------------------------------ # Get Genres @@ -49,8 +51,10 @@ GET {{media-store-service}}/MediaTypes ### ------------------------------------------------------------------------ # Get PlaylistTrack -GET {{media-store-service}}/PlaylistTrack -?$top=10&$expand=track,playlist +GET {{media-store-service}}/PlaylistTrack/$count +?$filter=playlist_ID eq 8 +# ?$count=true +# &$expand=track&$select=track ### ------------------------------------------------------------------------ # Get Playlists @@ -60,4 +64,4 @@ GET {{media-store-service}}/Playlists ### ------------------------------------------------------------------------ # Get Playlists GET {{media-store-service}}/Tracks -?$top=10&$expand=genre,mediaType,album \ No newline at end of file +# ?$top=10&$expand=genre,mediaType,album \ No newline at end of file diff --git a/media-store/util/importData.js b/media-store/util/importData.js index a17f9163..6636d974 100644 --- a/media-store/util/importData.js +++ b/media-store/util/importData.js @@ -86,14 +86,18 @@ const logProcessArgs = () => { const { name: targetEntityName, elements } = targetCSNEntities[index]; console.log(`[LOG]: Processing ${targetEntityName}`); const targetColumns = elementsToColumns(elements); // e.g. ['ID', ..., 'total', 'customer_ID'] - const insertQuery = constructInsertQuery(targetEntityName, targetColumns); // e.g. { INSERT: ... } + const insertQuery = constructInsertQuery(targetEntityName, targetColumns); - const srcEntityName = camelCaseToSnake(targetEntityName.split(".").pop()); // e.g. playlist_track + const srcEntityName = camelCaseToSnake(targetEntityName.split(".").pop()); const srcResultRows = await srcStorage.read(srcEntityName); // e.g. [ { AlbumId:1, ArtistId:1, Title:'some' }, ... ] - if (!srcResultRows || srcResultRows.length < ZERO_VALUE) { + console.log( + `[LOG] Skipping ${targetEntityName}. + There is no data provided in ${SRC_STORAGE_NAME}, ${srcEntityName}` + ); continue; } + const srcColumns = Object.keys(srcResultRows[FIRST_INDEX]); const columns = reorderTargetColumns(srcColumns, targetColumns); if (new Set(columns).size !== columns.length) {