#[1]gnikyt feed [2]gnikyt Code ramblings. Shopify Remix, Prisma, and Jest /* Jan 10, 2024 — 8.8KB */ Introduction Shopify has (once again) changed up their app framework and templates. This time, they’re utilizing Remix which is a React-based web framework, and Prisma which is an ORM. Recently, I developed a large loyalty application utlizing the new framework. Because this app is critical to the merchant’s shop, I wanted to ensure the business logic was working correctly. For my needs, I wanted to do both unit tests on specific code and integration tests. But, I was disappointed to see the framework setup offered by Shopify did not have testing built-in, nor any docs on how someone could get going on testing. With Prisma, you can mock the database returns from Prisma (example findMany) out of the box [3]for unit tests. const record = { id: 1, title: "Example", isDeleted: false, deletedAt: ..., createdAt: ..., updatedAt: ..., }; prismaMock.places.create.mockResolvedValue(record); const result = await PlacesRepository(prismaMock).create(record.title); // retur ns a model expect(result.toJSON()).toEqual(record); However, in terms of integration tests, a setup would be required to actually setup Prisma to communicate with a test database. It took a while to get working properly so I thought I would share the process. Testing Setup Note: This assumes you are using Remix with Typescript. Dependencies npm install --save-dev jest ts-jest Scripts Open package.json and add the following to the end of your scripts object: /* package.json */ "test": "jest", "test:coverage": "jest --coverage", "test:db:generate": "prisma generate", "test:db:deploy": "prisma migrate deploy", "test:db:reset": "prisma db push --force-reset --accept-data-loss --skip-generat e" These commands will be utilized later. Jest Open (or create) jest.config.js and input: /* jest.config.js */ module.exports = { preset: "ts-jest", testEnvironment: "node", setupFilesAfterEnv: ["./app/mocks.ts"], }; Git Ignore Add the following to your .gitignore: /prisma/*.sqlite /prisma/*.sqlite-journal /coverage Schema Update your prisma/schema.prisma file by changing the datasource.url value to env("DATABASE_URL): datasource db { provider = "sqlite" url = env("DATABASE_URL") } Mocks Setup Prisma mocks.prisma.ts Create a file app/mocks.prisma.ts with the following contents: // eslint-disable-next-line import/no-extraneous-dependencies import { afterAll, afterEach, beforeEach } from "@jest/globals"; import type { PrismaClient } from "@prisma/client"; import { execSync } from "child_process"; import { unlink } from "fs/promises"; import { existsSync } from "fs"; import * as path from "path"; // Path to generated types const typesPath = path.resolve("node_modules", ".prisma", "client", "index.d.ts" ); /** * Run an NPM command. * * @param script - Script to run. */ function npm(script: string) { execSync(["npm", "run", script].join(" "), { env: { ...process.env, NODE_ENV: "test", }, }); } /** * Generates the types, creates the database. */ async function createDatabase(client: PrismaClient) { if (!existsSync(typesPath)) { npm("test:db:generate"); } npm("test:db:deploy"); await client.$connect(); } /** * Destroy the database. * * @param client - Prisma client. */ async function destroyDatabase(client: PrismaClient) { // Disconnects Prisma await client.$disconnect(); // Remove database try { const dbPath = (process.env.DATABASE_URL as string).replace("file:", ""); await Promise.allSettled([unlink(dbPath), unlink(`${dbPath}-journal`)]); } catch (e) { // Do nothing, we don't care } } /** * Resets the database in between each test. */ // function resetDatabase() { // npm("test:db:reset"); // } /** * Seeds the database with initial data. * * @param client - Prisma client. */ function seedDatabase(client: PrismaClient) { // Seed shop session await client.session.create({ data: { id: "example.myshopify.com_id", shop: "example.myshopify.com", accessToken: "token", state: "", isOnline: false, }, ]); } /** * To be used on test suites which need this Prisma setup. */ export function prismaHooks(client: PrismaClient) { beforeEach(async () => { await createDatabase(client); await seedDatabase(client); }); afterEach(() => destroyDatabase(client)); } What this file does is utilize’s Jest’s global hooks (beforeAll, afterAll, etc) to setup and teardown the database for each test suite, if you invoke prismaHooks. Before each test is ran, the database creation process is triggered and the database is seeded. After each test is ran, the database deletion process is triggered. Finally, if types have not yet been generated by Prisma, it will be done once only. db.server.ts Create a file app/__mocks__/db.server.ts with the following content. This will be used by Jest to replace app/db.server.ts with this file. import { PrismaClient } from "@prisma/client"; import * as path from "path"; // Database path to ../prisma/test-[time].sqlite const dbPath = path.resolve(__dirname, "..", "..", "prisma", `test-${new Date(). getTime()}.sqlite`); // Set database URL override process.env.DATABASE_URL = `file:${dbPath}`; // Prisma client const prisma = new PrismaClient(); export default prisma; * dbPath sets the path of where our test SQLite database will live, prisma/test-[time].sqlite * dbPath is then is used to set the DATABASE_URL This ensures a unique database for each test suite. Jest Create a file app/mocks.ts with the following contents to tell Jest to use the mock Prisma setup. /* app/mocks.ts */ import { jest } from "@jest/globals"; jest.mock("./db.server"); Usage You are now ready to do integration tests! /* app/order/processor.test.ts */ import { describe, expect, it } from "@jest/globals"; import db from "../db.server"; import { prismaHooks } from "../mocks.prisma"; import { ShopifyClient, ShopifyFixture } from "../mocks.clients"; // etc... /* just an example... */ describe("order processor", () => { // Add our hooks for this suite prismaMocks(db); it("should insert order", async () => { const orderId = OrderId("gid://shopify/Order/1"); const customerId = CustomerId("gid://shopify/Customer/1"); const product1 = ProductId("gid://shopify/Product/1"); const prodcut2 = ProductId("gid://shopify/Product/2"); // Job data const jobData = { order: { id: orderId.toInt(), admin_graphql_id: orderId.toGql(), tags: [], total_price: "115.00", customer: { id: customerId.toInt(), }, line_items: [ { product_id: p1.toInt(), price: "100.00", quantity: 1, }, { product_id: p2.toInt(), price: "15.00", quantity: 1, }, ], }, shop: "example.myshopify.com", }; // Shopify GraphQL mock client const client = ShopifyClient( ShopifyFixture("product-no-tag", "product/no-override-tag"), ShopifyFixture("product-with-tag", "product/with-override-tag"), ShopifyFixture("add-tag", "tags-add-customer"), ); // Run the processor const [retOrderId, retOustomerId, retSum]: JobReturn = await processor({ db, client, job: { data: jobData }, }); expect(retOrderId.isSame(orderId)).toBe(true); expect(retCustomerId.isSame(customerId)).toBe(true); expect(retSum.isSame(Cents(3000))); }); }); Now, running npm run test will give us a testing result! npm run test > test:coverage > jest PASS app/order/processor.test.ts (10.12 s) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 10.44 s Success! I hope this helps you run integrations tests with Prisma and Jest. Tests running slow? There are several results on the web of people having issues of Jest running slow for Typescript. There are several solutions offered. Example: 1. maxWorkers flag: Add --maxWorkers=[num], example: npm run test -- --maxWorkers=3 2. runInBand flag: Add --runInBand which runs test in sync, example: npm run test -- --runInBand 3. Remove ts-node: ts-node is slow, you can try swapping to swc with it’s Jest support My app is fairly chunky, and in my testing of speeds the results are as followed: * Running as-is: 47 seconds * Running with maxWorkers=4: 38 seconds * Running with runInBand: 73 seconds [4]MD | [5]TXT | [6]CC-4.0 __________________________________________________________________ [7]Ty King Ty King A self-taught, seasoned, and versatile developer from Newfoundland. Crafting innovative solutions with care and expertise. See more [8]about me. [9]Github [10]LinkedIn [11]CV [12]RSS * * * * * * * * * * References Visible links: 1. /rss.xml 2. / 3. https://www.prisma.io/docs/orm/prisma-client/testing/unit-testing 4. /shopify-remix-prisma-and-jest/index.md 5. /shopify-remix-prisma-and-jest/index.txt 6. https://creativecommons.org/licenses/by/4.0/ 7. /about 8. /about 9. https://github.com/gnikyt 10. https://linkedin.com/in/gnikyt 11. /assets/files/cv.pdf 12. /rss.xml Hidden links: 14. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-1 15. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-2 16. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-3 17. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-4 18. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-5 19. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-6 20. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-7 21. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-8 22. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-9 23. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-10 24. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-11 25. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb1-12 26. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb3-1 27. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb3-2 28. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb3-3 29. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb3-4 30. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb3-5 31. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb3-6 32. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-1 33. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-2 34. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-3 35. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-4 36. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-5 37. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-6 38. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-7 39. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-8 40. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-9 41. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-10 42. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-11 43. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-12 44. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-13 45. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-14 46. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-15 47. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-16 48. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-17 49. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-18 50. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-19 51. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-20 52. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-21 53. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-22 54. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-23 55. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-24 56. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-25 57. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-26 58. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-27 59. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-28 60. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-29 61. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-30 62. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-31 63. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-32 64. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-33 65. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-34 66. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-35 67. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-36 68. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-37 69. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-38 70. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-39 71. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-40 72. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-41 73. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-42 74. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-43 75. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-44 76. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-45 77. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-46 78. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-47 79. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-48 80. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-49 81. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-50 82. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-51 83. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-52 84. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-53 85. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-54 86. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-55 87. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-56 88. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-57 89. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-58 90. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-59 91. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-60 92. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-61 93. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-62 94. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-63 95. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-64 96. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-65 97. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-66 98. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-67 99. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-68 100. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-69 101. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-70 102. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-71 103. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-72 104. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-73 105. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-74 106. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-75 107. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-76 108. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-77 109. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-78 110. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-79 111. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-80 112. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-81 113. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-82 114. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-83 115. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-84 116. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-85 117. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-86 118. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-87 119. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-88 120. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-89 121. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb6-90 122. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-1 123. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-2 124. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-3 125. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-4 126. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-5 127. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-6 128. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-7 129. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-8 130. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-9 131. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-10 132. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-11 133. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb7-12 134. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb8-1 135. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb8-2 136. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb8-3 137. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb8-4 138. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-1 139. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-2 140. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-3 141. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-4 142. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-5 143. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-6 144. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-7 145. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-8 146. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-9 147. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-10 148. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-11 149. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-12 150. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-13 151. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-14 152. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-15 153. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-16 154. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-17 155. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-18 156. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-19 157. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-20 158. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-21 159. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-22 160. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-23 161. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-24 162. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-25 163. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-26 164. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-27 165. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-28 166. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-29 167. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-30 168. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-31 169. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-32 170. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-33 171. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-34 172. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-35 173. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-36 174. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-37 175. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-38 176. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-39 177. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-40 178. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-41 179. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-42 180. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-43 181. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-44 182. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-45 183. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-46 184. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-47 185. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-48 186. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-49 187. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-50 188. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-51 189. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-52 190. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-53 191. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-54 192. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-55 193. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-56 194. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-57 195. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-58 196. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-59 197. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-60 198. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-61 199. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-62 200. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb9-63 201. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-1 202. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-2 203. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-3 204. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-4 205. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-5 206. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-6 207. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-7 208. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-8 209. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-9 210. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-10 211. localhost/tmp/lynxXXXXti5ST1/L758175-3973TMP.html#cb10-11