Initial Move from Azure DevOps to GitHub

This commit is contained in:
Steve Faulkner
2020-05-25 21:30:55 -05:00
commit 36581fb6d9
986 changed files with 195242 additions and 0 deletions

128
src/Juno/JunoClient.test.ts Normal file
View File

@@ -0,0 +1,128 @@
import ko from "knockout";
import { HttpStatusCodes } from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import { IPinnedRepo, JunoClient } from "./JunoClient";
const sampleDatabaseAccount: ViewModels.DatabaseAccount = {
id: "id",
name: "name",
location: "location",
type: "type",
kind: "kind",
tags: [],
properties: {
documentEndpoint: "documentEndpoint",
gremlinEndpoint: "gremlinEndpoint",
tableEndpoint: "tableEndpoint",
cassandraEndpoint: "cassandraEndpoint"
}
};
const samplePinnedRepos: IPinnedRepo[] = [
{
owner: "owner",
name: "name",
private: false,
branches: [
{
name: "name"
}
]
}
];
describe("Pinned repos", () => {
const junoClient = new JunoClient(ko.observable<ViewModels.DatabaseAccount>(sampleDatabaseAccount));
beforeEach(() => {
window.fetch = jest.fn().mockImplementation(() => {
return {
status: HttpStatusCodes.OK,
text: () => JSON.stringify(samplePinnedRepos)
};
});
});
afterEach(() => {
jest.resetAllMocks();
});
it("updatePinnedRepos invokes pinned repos subscribers", async () => {
const callback = jest.fn().mockImplementation((pinnedRepos: IPinnedRepo[]) => {});
junoClient.subscribeToPinnedRepos(callback);
const response = await junoClient.updatePinnedRepos(samplePinnedRepos);
expect(response.status).toBe(HttpStatusCodes.OK);
expect(callback).toBeCalledWith(samplePinnedRepos);
});
it("getPinnedRepos invokes pinned repos subscribers", async () => {
const callback = jest.fn().mockImplementation((pinnedRepos: IPinnedRepo[]) => {});
junoClient.subscribeToPinnedRepos(callback);
const response = await junoClient.getPinnedRepos("scope");
expect(response.status).toBe(HttpStatusCodes.OK);
expect(callback).toBeCalledWith(samplePinnedRepos);
});
});
describe("GitHub", () => {
const junoClient = new JunoClient(ko.observable<ViewModels.DatabaseAccount>(sampleDatabaseAccount));
afterEach(() => {
jest.resetAllMocks();
});
it("getGitHubToken", async () => {
let fetchUrl: string;
window.fetch = jest.fn().mockImplementation((url: string) => {
fetchUrl = url;
return {
status: HttpStatusCodes.OK,
text: () => JSON.stringify({ access_token: "token" })
};
});
const response = await junoClient.getGitHubToken("code");
expect(response.status).toBe(HttpStatusCodes.OK);
expect(response.data.access_token).toBeDefined();
expect(window.fetch).toBeCalled();
const fetchUrlParams = new URLSearchParams(new URL(fetchUrl).search);
let fetchUrlParamsCount = 0;
fetchUrlParams.forEach(() => fetchUrlParamsCount++);
expect(fetchUrlParamsCount).toBe(2);
expect(fetchUrlParams.get("code")).toBeDefined();
expect(fetchUrlParams.get("client_id")).toBeDefined();
});
it("deleteAppauthorization", async () => {
let fetchUrl: string;
window.fetch = jest.fn().mockImplementation((url: string) => {
fetchUrl = url;
return {
status: HttpStatusCodes.NoContent,
text: () => undefined as string
};
});
const response = await junoClient.deleteAppAuthorization("token");
expect(response.status).toBe(HttpStatusCodes.NoContent);
expect(window.fetch).toBeCalled();
const fetchUrlParams = new URLSearchParams(new URL(fetchUrl).search);
let fetchUrlParamsCount = 0;
fetchUrlParams.forEach(() => fetchUrlParamsCount++);
expect(fetchUrlParamsCount).toBe(2);
expect(fetchUrlParams.get("access_token")).toBeDefined();
expect(fetchUrlParams.get("client_id")).toBeDefined();
});
});

151
src/Juno/JunoClient.ts Normal file
View File

@@ -0,0 +1,151 @@
import ko from "knockout";
import { HttpStatusCodes } from "../Common/Constants";
import { config } from "../Config";
import * as ViewModels from "../Contracts/ViewModels";
import { IGitHubResponse } from "../GitHub/GitHubClient";
import { IGitHubOAuthToken } from "../GitHub/GitHubOAuthService";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
export interface IJunoResponse<T> {
status: number;
data: T;
}
export interface IPinnedRepo {
owner: string;
name: string;
private: boolean;
branches: IPinnedBranch[];
}
export interface IPinnedBranch {
name: string;
}
export class JunoClient {
private cachedPinnedRepos: ko.Observable<IPinnedRepo[]>;
constructor(public databaseAccount: ko.Observable<ViewModels.DatabaseAccount>) {
this.cachedPinnedRepos = ko.observable<IPinnedRepo[]>([]);
}
public subscribeToPinnedRepos(callback: ko.SubscriptionCallback<IPinnedRepo[], void>): ko.Subscription {
return this.cachedPinnedRepos.subscribe(callback);
}
public async getPinnedRepos(scope: string): Promise<IJunoResponse<IPinnedRepo[]>> {
const response = await window.fetch(`${this.getJunoGitHubUrl()}/pinnedrepos`, {
headers: this.getHeaders()
});
let pinnedRepos: IPinnedRepo[];
if (response.status === HttpStatusCodes.OK) {
pinnedRepos = JSON.parse(await response.text());
// In case we're restricted to public only scope, we return only public repos
if (scope === AuthorizeAccessComponent.Scopes.Public.key) {
pinnedRepos = pinnedRepos.filter(repo => !repo.private);
}
this.cachedPinnedRepos(pinnedRepos);
}
return {
status: response.status,
data: pinnedRepos
};
}
public async updatePinnedRepos(repos: IPinnedRepo[]): Promise<IJunoResponse<undefined>> {
const response = await window.fetch(`${this.getJunoGitHubUrl()}/pinnedrepos`, {
method: "PUT",
body: JSON.stringify(repos),
headers: this.getHeaders()
});
if (response.status === HttpStatusCodes.OK) {
this.cachedPinnedRepos(repos);
}
return {
status: response.status,
data: undefined
};
}
public async deleteGitHubInfo(): Promise<IJunoResponse<undefined>> {
const response = await window.fetch(this.getJunoGitHubUrl(), {
method: "DELETE",
headers: this.getHeaders()
});
return {
status: response.status,
data: undefined
};
}
public async getGitHubToken(code: string): Promise<IGitHubResponse<IGitHubOAuthToken>> {
const githubParams = JunoClient.getGitHubClientParams();
githubParams.append("code", code);
const response = await window.fetch(`${this.getJunoGitHubUrl()}/token?${githubParams.toString()}`, {
headers: this.getHeaders()
});
let data: IGitHubOAuthToken;
const body = await response.text();
if (body) {
data = JSON.parse(body);
} else {
data = {
error: response.statusText
};
}
return {
status: response.status,
data
};
}
public async deleteAppAuthorization(token: string): Promise<IJunoResponse<string>> {
const githubParams = JunoClient.getGitHubClientParams();
githubParams.append("access_token", token);
const response = await window.fetch(`${this.getJunoGitHubUrl()}/token?${githubParams.toString()}`, {
method: "DELETE",
headers: this.getHeaders()
});
return {
status: response.status,
data: await response.text()
};
}
private getJunoGitHubUrl(): string {
return `${config.JUNO_ENDPOINT}/api/notebooks/${this.databaseAccount().name}/github`;
}
private getHeaders(): HeadersInit {
const authorizationHeader = getAuthorizationHeader();
return {
[authorizationHeader.header]: authorizationHeader.token,
"content-type": "application/json"
};
}
public static getGitHubClientParams(): URLSearchParams {
const githubParams = new URLSearchParams({
client_id: config.GITHUB_CLIENT_ID
});
if (config.GITHUB_CLIENT_SECRET) {
githubParams.append("client_secret", config.GITHUB_CLIENT_SECRET);
}
return githubParams;
}
}