60e2149072
Tasks 16-20: Online Board Tests (Search/Filter, Tabs, Flight List, Details Modal, Time/Date) - Task 16: Search & Filter tests (37 tests) - departure/arrival cities, passenger count, cabin class - Task 17: Arrival/Departure Tabs tests (45 tests) - tab switching, flight display, sorting - Task 18: Flight List View tests (50 tests) - display, sorting, filtering, pagination, loading states - Task 19: Flight Details Modal tests (40 tests) - opening/closing, content display, actions - Task 20: Time & Date Filter tests (43 tests) - date selection, time ranges, calendar navigation Tasks 21-25: Flight Details Tests (Flight Info, Passengers, Seats, Services, Fares) - Task 21: Flight Info Display tests (40 tests) - basic info, airports, route visualization, timeline - Task 22: Passenger Info tests (50 tests) - passenger list, details, services, special requirements - Task 23: Seat Selection tests (50 tests) - seat map, selection, categories, recommendations - Task 24: Service Selection tests (25 tests) - baggage, meals, seats, summary - Task 25: Fare Display tests (55 tests) - fare breakdown, comparisons, discounts, refunds All tests follow AAA pattern and use data-testid selectors matching Angular version. Total: 245 tests across 10 feature suites.
162 lines
4.4 KiB
JavaScript
162 lines
4.4 KiB
JavaScript
const CacheSemantics = require('http-cache-semantics')
|
|
const Negotiator = require('negotiator')
|
|
const ssri = require('ssri')
|
|
|
|
// options passed to http-cache-semantics constructor
|
|
const policyOptions = {
|
|
shared: false,
|
|
ignoreCargoCult: true,
|
|
}
|
|
|
|
// a fake empty response, used when only testing the
|
|
// request for storability
|
|
const emptyResponse = { status: 200, headers: {} }
|
|
|
|
// returns a plain object representation of the Request
|
|
const requestObject = (request) => {
|
|
const _obj = {
|
|
method: request.method,
|
|
url: request.url,
|
|
headers: {},
|
|
compress: request.compress,
|
|
}
|
|
|
|
request.headers.forEach((value, key) => {
|
|
_obj.headers[key] = value
|
|
})
|
|
|
|
return _obj
|
|
}
|
|
|
|
// returns a plain object representation of the Response
|
|
const responseObject = (response) => {
|
|
const _obj = {
|
|
status: response.status,
|
|
headers: {},
|
|
}
|
|
|
|
response.headers.forEach((value, key) => {
|
|
_obj.headers[key] = value
|
|
})
|
|
|
|
return _obj
|
|
}
|
|
|
|
class CachePolicy {
|
|
constructor ({ entry, request, response, options }) {
|
|
this.entry = entry
|
|
this.request = requestObject(request)
|
|
this.response = responseObject(response)
|
|
this.options = options
|
|
this.policy = new CacheSemantics(this.request, this.response, policyOptions)
|
|
|
|
if (this.entry) {
|
|
// if we have an entry, copy the timestamp to the _responseTime
|
|
// this is necessary because the CacheSemantics constructor forces
|
|
// the value to Date.now() which means a policy created from a
|
|
// cache entry is likely to always identify itself as stale
|
|
this.policy._responseTime = this.entry.metadata.time
|
|
}
|
|
}
|
|
|
|
// static method to quickly determine if a request alone is storable
|
|
static storable (request, options) {
|
|
// no cachePath means no caching
|
|
if (!options.cachePath) {
|
|
return false
|
|
}
|
|
|
|
// user explicitly asked not to cache
|
|
if (options.cache === 'no-store') {
|
|
return false
|
|
}
|
|
|
|
// we only cache GET and HEAD requests
|
|
if (!['GET', 'HEAD'].includes(request.method)) {
|
|
return false
|
|
}
|
|
|
|
// otherwise, let http-cache-semantics make the decision
|
|
// based on the request's headers
|
|
const policy = new CacheSemantics(requestObject(request), emptyResponse, policyOptions)
|
|
return policy.storable()
|
|
}
|
|
|
|
// returns true if the policy satisfies the request
|
|
satisfies (request) {
|
|
const _req = requestObject(request)
|
|
if (this.request.headers.host !== _req.headers.host) {
|
|
return false
|
|
}
|
|
|
|
if (this.request.compress !== _req.compress) {
|
|
return false
|
|
}
|
|
|
|
const negotiatorA = new Negotiator(this.request)
|
|
const negotiatorB = new Negotiator(_req)
|
|
|
|
if (JSON.stringify(negotiatorA.mediaTypes()) !== JSON.stringify(negotiatorB.mediaTypes())) {
|
|
return false
|
|
}
|
|
|
|
if (JSON.stringify(negotiatorA.languages()) !== JSON.stringify(negotiatorB.languages())) {
|
|
return false
|
|
}
|
|
|
|
if (JSON.stringify(negotiatorA.encodings()) !== JSON.stringify(negotiatorB.encodings())) {
|
|
return false
|
|
}
|
|
|
|
if (this.options.integrity) {
|
|
return ssri.parse(this.options.integrity).match(this.entry.integrity)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// returns true if the request and response allow caching
|
|
storable () {
|
|
return this.policy.storable()
|
|
}
|
|
|
|
// NOTE: this is a hack to avoid parsing the cache-control
|
|
// header ourselves, it returns true if the response's
|
|
// cache-control contains must-revalidate
|
|
get mustRevalidate () {
|
|
return !!this.policy._rescc['must-revalidate']
|
|
}
|
|
|
|
// returns true if the cached response requires revalidation
|
|
// for the given request
|
|
needsRevalidation (request) {
|
|
const _req = requestObject(request)
|
|
// force method to GET because we only cache GETs
|
|
// but can serve a HEAD from a cached GET
|
|
_req.method = 'GET'
|
|
return !this.policy.satisfiesWithoutRevalidation(_req)
|
|
}
|
|
|
|
responseHeaders () {
|
|
return this.policy.responseHeaders()
|
|
}
|
|
|
|
// returns a new object containing the appropriate headers
|
|
// to send a revalidation request
|
|
revalidationHeaders (request) {
|
|
const _req = requestObject(request)
|
|
return this.policy.revalidationHeaders(_req)
|
|
}
|
|
|
|
// returns true if the request/response was revalidated
|
|
// successfully. returns false if a new response was received
|
|
revalidated (request, response) {
|
|
const _req = requestObject(request)
|
|
const _res = responseObject(response)
|
|
const policy = this.policy.revalidatedPolicy(_req, _res)
|
|
return !policy.modified
|
|
}
|
|
}
|
|
|
|
module.exports = CachePolicy
|