Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tbrannam/cdad27d58a05bce2eed5d692f8af89ef to your computer and use it in GitHub Desktop.
Save tbrannam/cdad27d58a05bce2eed5d692f8af89ef to your computer and use it in GitHub Desktop.
experiment assembly script
import { URL } from "@fastly/as-url";
import { RegExp } from "assemblyscript-regex";
import { Dictionary } from "@fastly/as-compute"
import { JSON } from "assemblyscript-json";
// import { md5 } from "hash-wasm";
function normalizePath(url: URL): URL {
const filenameRegEx = new RegExp(".*\.([a-zA-Z0-9]{1,11})$", "g");
let path = url.pathname;
// if url doesn't have a filename extension, then add / and/or index.html
const match = filenameRegEx.exec(path);
if (match == null) {
if (path.endsWith("/") == false) {
// url.pathname = path + "/index.html"
path = path + "/index.html";
} else {
path = path + "index.html"
}
const result = new URL(url.toString());
return result
}
}
class MockDictionary extends Dictionary {
constructor(name: string, private mockValues: Map<string, string | null>) {
super(name)
}
get(key: string): string | null {
if (this.mockValues.has(key)) {
return this.mockValues.get(key);
}
return null;
}
contains(key: string): boolean {
const strOrNull = this.mockValues.has(key);
return (strOrNull != null)
}
}
function versionPath(currentVersionDictionary: Dictionary, publishedVersionsDictionary: Dictionary): string | null {
const versionKey: string | null = currentVersionDictionary.get("default");
if (versionKey) {
return publishedVersionsDictionary.get(versionKey);
}
return null;
}
function getExperimentPrefix(versionHash: string, experimentsDictionary: Dictionary): string {
// the format of the experimentsDictionary is convoluted, but worked around VCL limitations (no loops)
// we will need to revisit this format to something more appropriate for the language
// ideas were based around Fastly AB Test example https://developer.fastly.com/solutions/examples/a/b-testing
const testKey = versionHash + "-tests";
const testsValue = <string | null>experimentsDictionary.get(testKey);
if (!testsValue) {
return '';
}
if (testsValue == "default") {
return <string>testsValue;
}
const tests = (testsValue as string).split(',').map<string>((val: string) => val.trim());
let acc = ''
for (let ii = 0; ii < tests.length; ii++) {
const current = tests[ii];
const currentKey = versionHash + "-test-" + current;
hash(currentKey);
// log(currentKey);
const currentExperiment = <string>experimentsDictionary.get(currentKey);
const pick = seededRandomRange(0, currentExperiment.length, 1301);
acc = acc + currentExperiment.charAt(pick);
}
// log(acc);
return acc;
}
function seededRandomRange(from: i32, to: i32, seed: i64): i32 {
NativeMath.seedRandom(seed);
return <i32>NativeMath.floor((to - from) * NativeMath.random()) + from
}
// function hashCode(str: string): number {
// return str.split('').reduce((prevHash, currVal) =>
// (((prevHash << 5) - prevHash) + currVal.charCodeAt(0)) | 0, 0);
// }
const CURRENT_VERSION_HASH = "0d66e7b9ac83c881";
describe("normalizePath", () => {
it("do the thing", () => {
expect(seededRandomRange(1, 10, 123)).toBe(6);
md5("foo");
})
// it("should read the current version path", () => {
// const currentVersionHash = currentVersionDictionary.get('default');
// expect(currentVersionHash).toBe(CURRENT_VERSION_HASH);
// })
it("returns versioned prefix", () => {
const path = "/home/index.html"
const CURRENT_VERSION_HASH = "0d66e7b9ac83c881";
const versionMap = new Map<string, string | null>();
versionMap.set('default', CURRENT_VERSION_HASH);
const currentVersionDictionary = new MockDictionary('current_version', versionMap);
const publishedVersionMap = new Map<string, string | null>();
publishedVersionMap.set('0d66e7b9ac83c881', '20210322_1203.59329');
const publishedVersionsDictionary = new MockDictionary('published_versions', publishedVersionMap);
const experimentMap = new Map<string, string | null>();
experimentMap.set('0d66e7b9ac83c881-tests', 'cartlocation, Checkout_Card_With_Name_Field');
experimentMap.set('0d66e7b9ac83c881-test-cartlocation', 'AAAAAAAAAB');
experimentMap.set('0d66e7b9ac83c881-test-Checkout_Card_With_Name_Field', 'AB');
const experimentsDictionary = new MockDictionary('experiments', experimentMap);
const currentVersionHash = currentVersionDictionary.get('default');
if (currentVersionHash) {
log('doit');
const publishedVersionPrefix = versionPath(currentVersionDictionary, publishedVersionsDictionary);
expect(publishedVersionPrefix).toBe("20210322_1203.59329");
const experimentPrefix = getExperimentPrefix(currentVersionHash, experimentsDictionary);
log(experimentPrefix);
const s3Path = [
null,
null,
path
].join('/').replaceAll('//', '/');
log(s3Path);
}
})
it("should append /index.html when url has no file extension", () => {
const url = new URL("http://www.example.com");
expect(url.pathname).toBe("/")
normalizePath(url);
expect(url.pathname).toBe("/index.html")
})
xit("should append index.html when url has no file extension or trailing slash", () => {
const url = new URL("http://www.example.com/");
normalizePath(url);
expect(url.pathname).toBe("/index.html")
})
xit("should do nothing when called with a file extension", () => {
const url = new URL("http://www.example.com/file.jpg");
normalizePath(url);
expect(url.pathname).toBe("/file.jpg")
})
it("should be 42", () => {
expect(hashCode('foofffff')).toBe(0);
});
// it("should be the same reference", () => {
// let ref = new Vec3();
// expect<Vec3>(ref).toBe(ref, "Reference Equality");
// });
// it("should perform a memory comparison", () => {
// let a = new Vec3(1, 2, 3);
// let b = new Vec3(1, 2, 3);
// expect<Vec3>(a).toStrictEqual(
// b,
// "a and b have the same values, (discluding child references)",
// );
// });
// it("should compare strings", () => {
// expect<string>("a=" + "200").toBe("a=200", "both strings are equal");
// });
// it("should compare values", () => {
// expect<i32>(10).toBeLessThan(200);
// expect<i32>(1000).toBeGreaterThan(200);
// expect<i32>(1000).toBeGreaterThanOrEqual(1000);
// expect<i32>(1000).toBeLessThanOrEqual(1000);
// });
// it("can log some values to the console", () => {
// log<string>("Hello world!"); // strings!
// log<f64>(3.1415); // floats!
// log<u8>(244); // integers!
// log<u64>(0xffffffff); // long values!
// log<ArrayBuffer>(new ArrayBuffer(50)); // bytes!
// });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment