Merge pull request #595 from bitwiseman/issue/rate-limit

Adjust GHRateLimit to system time instead of depending on synchronization
This commit is contained in:
Liam Newman
2019-11-12 13:01:58 -08:00
committed by GitHub
82 changed files with 1617 additions and 386 deletions

View File

@@ -1,43 +1,385 @@
package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.Objects;
import java.util.logging.Logger;
import static java.util.logging.Level.FINEST;
/**
* Rate limit.
*
* @author Kohsuke Kawaguchi
*/
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API")
public class GHRateLimit {
/**
* Remaining calls that can be made.
*
* @deprecated This value should never have been made public. Use {@link #getRemaining()}
*/
@Deprecated
public int remaining;
/**
* Allotted API call per hour.
*
* @deprecated This value should never have been made public. Use {@link #getLimit()}
*/
@Deprecated
public int limit;
/**
* The time at which the current rate limit window resets in UTC epoch seconds.
* NOTE: that means to
*
* @deprecated This value should never have been made public. Use {@link #getResetDate()}
*/
@Deprecated
public Date reset;
@Nonnull
private final Record core;
@Nonnull
private final Record search;
@Nonnull
private final Record graphql;
@Nonnull
private final Record integrationManifest;
static GHRateLimit Unknown() {
return new GHRateLimit(new UnknownLimitRecord(), new UnknownLimitRecord(), new UnknownLimitRecord(), new UnknownLimitRecord());
}
static GHRateLimit fromHeaderRecord(Record header) {
return new GHRateLimit(header, new UnknownLimitRecord(), new UnknownLimitRecord(), new UnknownLimitRecord());
}
@JsonCreator
GHRateLimit(@Nonnull @JsonProperty("core") Record core,
@Nonnull @JsonProperty("search") Record search,
@Nonnull @JsonProperty("graphql") Record graphql,
@Nonnull @JsonProperty("integration_manifest") Record integrationManifest) {
this.core = core;
this.search = search;
this.graphql = graphql;
this.integrationManifest = integrationManifest;
// Deprecated fields
this.remaining = core.getRemaining();
this.limit = core.getLimit();
this.reset = new Date(core.getResetEpochSeconds());
}
/**
* Non-epoch date
* Returns the date at which the Core API rate limit will reset.
*
* @return the calculated date at which the rate limit has or will reset.
*/
@SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR",
justification = "The value comes from JSON deserialization")
@Nonnull
public Date getResetDate() {
return new Date(reset.getTime() * 1000);
return getCore().getResetDate();
}
/**
* Gets the remaining number of Core APIs requests allowed before this connection will be throttled.
*
* @return an integer
* @since 1.100
*/
public int getRemaining() {
return getCore().getRemaining();
}
/**
* Gets the total number of Core API calls per hour allotted for this connection.
*
* @return an integer
* @since 1.100
*/
public int getLimit() {
return getCore().getLimit();
}
/**
* Gets the time in epoch seconds when the Core API rate limit will reset.
*
* @return a long
* @since 1.100
*/
public long getResetEpochSeconds() {
return getCore().getResetEpochSeconds();
}
/**
* Whether the rate limit reset date for this instance has passed.
*
* @return true if the rate limit reset date has passed. Otherwise false.
* @since 1.100
*/
public boolean isExpired() {
return getCore().isExpired();
}
/**
* The core object provides your rate limit status for all non-search-related resources in the REST API.
*
* @return a rate limit record
* @since 1.100
*/
@Nonnull
public Record getCore() {
return core;
}
/**
* The search object provides your rate limit status for the Search API.
* TODO: integrate with header limit updating. Issue #605.
*
* @return a rate limit record
*/
@Nonnull
Record getSearch() {
return search;
}
/**
* The graphql object provides your rate limit status for the GraphQL API.
* TODO: integrate with header limit updating. Issue #605.
*
* @return a rate limit record
*/
@Nonnull
Record getGraphQL() {
return graphql;
}
/**
* The integration_manifest object provides your rate limit status for the GitHub App Manifest code conversion endpoint.
* TODO: integrate with header limit updating. Issue #605.
*
* @return a rate limit record
*/
@Nonnull
Record getIntegrationManifest() {
return integrationManifest;
}
@Override
public String toString() {
return "GHRateLimit{" +
"remaining=" + remaining +
", limit=" + limit +
return "GHRateLimit {" +
"core " + getCore().toString() +
"search " + getSearch().toString() +
"graphql " + getGraphQL().toString() +
"integrationManifest " + getIntegrationManifest().toString() +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GHRateLimit rateLimit = (GHRateLimit) o;
return getCore().equals(rateLimit.getCore()) &&
getSearch().equals(rateLimit.getSearch()) &&
getGraphQL().equals(rateLimit.getGraphQL()) &&
getIntegrationManifest().equals(rateLimit.getIntegrationManifest());
}
@Override
public int hashCode() {
return Objects.hash(getCore(), getSearch(), getGraphQL(), getIntegrationManifest());
}
/**
* A limit record used as a placeholder when the the actual limit is not known.
*
* Has a large limit and long duration so that it will doesn't expire too often.
*
* @since 1.100*
*/
public static class UnknownLimitRecord extends Record {
// One hour
private static final long unknownLimitResetSeconds = 60L * 60L;
static final int unknownLimit = 1000000;
static final int unknownRemaining = 999999;
private UnknownLimitRecord() {
super(unknownLimit, unknownRemaining, System.currentTimeMillis() / 1000L + unknownLimitResetSeconds);
}
}
/**
* A rate limit record.
* @since 1.100
*/
public static class Record {
/**
* Remaining calls that can be made.
*/
private final int remaining;
/**
* Allotted API call per hour.
*/
private final int limit;
/**
* The time at which the current rate limit window resets in UTC epoch seconds.
*/
private final long resetEpochSeconds;
/**
* EpochSeconds time (UTC) at which this instance was created.
*/
private final long createdAtEpochSeconds = System.currentTimeMillis() / 1000;
/**
* The calculated time at which the rate limit will reset.
* Recalculated if {@link #recalculateResetDate} is called.
*/
@Nonnull
private Date resetDate;
@JsonCreator
public Record(@JsonProperty("limit") int limit,
@JsonProperty("remaining") int remaining,
@JsonProperty("reset")long resetEpochSeconds) {
this(limit, remaining, resetEpochSeconds, null);
}
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD",
justification = "Deprecated")
public Record(int limit, int remaining, long resetEpochSeconds, String updatedAt) {
this.limit = limit;
this.remaining = remaining;
this.resetEpochSeconds = resetEpochSeconds;
this.resetDate = recalculateResetDate(updatedAt);
}
/**
* Recalculates the reset date using the server response date to calculate a time duration
* and then add that to the local created time for this record.
*
* @param updatedAt a string date in RFC 1123
* @return reset date based on the passed date
*/
Date recalculateResetDate(String updatedAt) {
long updatedAtEpochSeconds = createdAtEpochSeconds;
if (!StringUtils.isBlank(updatedAt)) {
try {
// Get the server date and reset data, will always return a time in GMT
updatedAtEpochSeconds = ZonedDateTime.parse(updatedAt, DateTimeFormatter.RFC_1123_DATE_TIME).toEpochSecond();
} catch (DateTimeParseException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed Date header value " + updatedAt, e);
}
}
}
// This may seem odd but it results in an accurate or slightly pessimistic reset date
// based on system time rather than on the system being in sync with the server
long calculatedSecondsUntilReset = resetEpochSeconds - updatedAtEpochSeconds;
return resetDate = new Date((createdAtEpochSeconds + calculatedSecondsUntilReset) * 1000);
}
/**
* Gets the remaining number of requests allowed before this connection will be throttled.
*
* @return an integer
*/
public int getRemaining() {
return remaining;
}
/**
* Gets the total number of API calls per hour allotted for this connection.
*
* @return an integer
*/
public int getLimit() {
return limit;
}
/**
* Gets the time in epoch seconds when the rate limit will reset.
*
* @return a long
*/
public long getResetEpochSeconds() {
return resetEpochSeconds;
}
/**
* Whether the rate limit reset date indicated by this instance is in the
*
* @return true if the rate limit reset date has passed. Otherwise false.
*/
public boolean isExpired() {
return getResetDate().getTime() < System.currentTimeMillis();
}
/**
* Returns the date at which the rate limit will reset.
*
* @return the calculated date at which the rate limit has or will reset.
*/
@Nonnull
public Date getResetDate() {
return new Date(resetDate.getTime());
}
@Override
public String toString() {
return "{" +
"remaining=" + getRemaining() +
", limit=" + getLimit() +
", resetDate=" + getResetDate() +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Record record = (Record) o;
return getRemaining() == record.getRemaining() &&
getLimit() == record.getLimit() &&
getResetEpochSeconds() == record.getResetEpochSeconds() &&
getResetDate().equals(record.getResetDate());
}
@Override
public int hashCode() {
return Objects.hash(getRemaining(), getLimit(), getResetEpochSeconds(), getResetDate());
}
}
private static final Logger LOGGER = Logger.getLogger(Requester.class.getName());
}

View File

@@ -31,6 +31,7 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -315,28 +316,58 @@ public class GitHub {
* Gets the current rate limit.
*/
public GHRateLimit getRateLimit() throws IOException {
GHRateLimit rateLimit;
try {
return rateLimit = retrieve().to("/rate_limit", JsonRateLimit.class).rate;
rateLimit = retrieve().to("/rate_limit", JsonRateLimit.class).resources;
} catch (FileNotFoundException e) {
// GitHub Enterprise doesn't have the rate limit, so in that case
// return some big number that's not too big.
// see issue #78
GHRateLimit r = new GHRateLimit();
r.limit = r.remaining = 1000000;
long hour = 60L * 60L; // this is madness, storing the date as seconds in a Date object
r.reset = new Date(System.currentTimeMillis() / 1000L + hour);
return rateLimit = r;
// GitHub Enterprise doesn't have the rate limit
// return a default rate limit that
rateLimit = GHRateLimit.Unknown();
}
return this.rateLimit = rateLimit;
}
/**
* Update the Rate Limit with the latest info from response header.
* Due to multi-threading requests might complete out of order, we want to pick the one with the most recent info from the server.
*
* @param observed {@link GHRateLimit.Record} constructed from the response header information
*/
void updateCoreRateLimit(@Nonnull GHRateLimit.Record observed) {
synchronized (headerRateLimitLock) {
if (headerRateLimit == null || shouldReplace(observed, headerRateLimit.getCore())) {
headerRateLimit = GHRateLimit.fromHeaderRecord(observed);
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
}
}
}
/*package*/ void updateRateLimit(@Nonnull GHRateLimit observed) {
synchronized (headerRateLimitLock) {
if (headerRateLimit == null
|| headerRateLimit.getResetDate().getTime() < observed.getResetDate().getTime()
|| headerRateLimit.remaining > observed.remaining) {
headerRateLimit = observed;
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
}
/**
* Update the Rate Limit with the latest info from response header.
* Due to multi-threading requests might complete out of order, we want to pick the one with the most recent info from the server.
* Header date is only accurate to the second, so we look at the information in the record itself.
*
* {@link GHRateLimit.UnknownLimitRecord}s are always replaced by regular {@link GHRateLimit.Record}s.
* Regular {@link GHRateLimit.Record}s are never replaced by {@link GHRateLimit.UnknownLimitRecord}s.
* Candidates with resetEpochSeconds later than current record are more recent.
* Candidates with the same reset and a lower remaining count are more recent.
* Candidates with an earlier reset are older.
*
* @param candidate {@link GHRateLimit.Record} constructed from the response header information
* @param current the current {@link GHRateLimit.Record} record
*/
static boolean shouldReplace(@Nonnull GHRateLimit.Record candidate, @Nonnull GHRateLimit.Record current) {
if (candidate instanceof GHRateLimit.UnknownLimitRecord && !(current instanceof GHRateLimit.UnknownLimitRecord)) {
// Unknown candidate never replaces a regular record
return false;
} else if (current instanceof GHRateLimit.UnknownLimitRecord && !(candidate instanceof GHRateLimit.UnknownLimitRecord)) {
// Any real record should replace an unknown Record.
return true;
} else {
// records of the same type compare to each other as normal.
return current.getResetEpochSeconds() < candidate.getResetEpochSeconds()
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds() && current.getRemaining() > candidate.getRemaining());
}
}
@@ -362,12 +393,12 @@ public class GitHub {
@Nonnull
public GHRateLimit rateLimit() throws IOException {
synchronized (headerRateLimitLock) {
if (headerRateLimit != null) {
if (headerRateLimit != null && !headerRateLimit.isExpired()) {
return headerRateLimit;
}
}
GHRateLimit rateLimit = this.rateLimit;
if (rateLimit == null || rateLimit.getResetDate().getTime() < System.currentTimeMillis()) {
if (rateLimit == null || rateLimit.isExpired()) {
rateLimit = getRateLimit();
}
return rateLimit;

View File

@@ -4,5 +4,7 @@ package org.kohsuke.github;
* @author Kohsuke Kawaguchi
*/
class JsonRateLimit {
GHRateLimit rate;
GHRateLimit resources;
}

View File

@@ -46,7 +46,6 @@ import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -352,54 +351,57 @@ class Requester {
}
private void noteRateLimit(String tailApiUrl) {
if ("/rate_limit".equals(tailApiUrl)) {
// the rate_limit API is "free"
return;
}
if (tailApiUrl.startsWith("/search")) {
// the search API uses a different rate limit
return;
}
String limit = uc.getHeaderField("X-RateLimit-Limit");
if (StringUtils.isBlank(limit)) {
String limitString = uc.getHeaderField("X-RateLimit-Limit");
if (StringUtils.isBlank(limitString)) {
// if we are missing a header, return fast
return;
}
String remaining = uc.getHeaderField("X-RateLimit-Remaining");
if (StringUtils.isBlank(remaining)) {
String remainingString = uc.getHeaderField("X-RateLimit-Remaining");
if (StringUtils.isBlank(remainingString)) {
// if we are missing a header, return fast
return;
}
String reset = uc.getHeaderField("X-RateLimit-Reset");
if (StringUtils.isBlank(reset)) {
String resetString = uc.getHeaderField("X-RateLimit-Reset");
if (StringUtils.isBlank(resetString)) {
// if we are missing a header, return fast
return;
}
GHRateLimit observed = new GHRateLimit();
int limit, remaining;
long reset;
try {
observed.limit = Integer.parseInt(limit);
limit = Integer.parseInt(limitString);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limit, e);
LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limitString, e);
}
return;
}
try {
observed.remaining = Integer.parseInt(remaining);
remaining = Integer.parseInt(remainingString);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remaining, e);
LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remainingString, e);
}
return;
}
try {
observed.reset = new Date(Long.parseLong(reset)); // this is madness, storing the date as seconds
root.updateRateLimit(observed);
reset = Long.parseLong(resetString);
} catch (NumberFormatException e) {
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + reset, e);
LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + resetString, e);
}
return;
}
GHRateLimit.Record observed = new GHRateLimit.Record(limit, remaining, reset, uc.getHeaderField("Date"));
root.updateCoreRateLimit(observed);
}
public String getResponseHeader(String header) {
@@ -706,6 +708,9 @@ class Requester {
}
} else if (readValue instanceof GHObject) {
setResponseHeaders((GHObject) readValue);
} else if (readValue instanceof JsonRateLimit) {
// if we're getting a GHRateLimit it needs the server date
((JsonRateLimit)readValue).resources.getCore().recalculateResetDate(uc.getHeaderField("Date"));
}
return readValue;
}

View File

@@ -0,0 +1,425 @@
package org.kohsuke.github;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.apache.commons.io.FileUtils;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.kohsuke.github.extras.okhttp3.OkHttpConnector;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
/**
* Test showing the behavior of OkHttpConnector with and without cache.
* <p>
* Key take aways:
*
* <ul>
* <li>These tests are artificial and intended to highlight the differences
* in behavior between scenarios. However, the differences they indicate are stark.</li>
* <li>Caching reduces rate limit consumption by at least a factor of two in even the simplest case.</li>
* <li>The OkHttp cache is pretty smart and will often connect read and write requests made
* on the same client and invalidate caches.</li>
* <li>Changes made outside the current client cause the OkHttp cache to return stale data.
* This is expected and correct behavior.</li>
* <li>"max-age=0" addresses the problem of external changes by revalidating caches for each request.
* This produces the same number of requests as OkHttp without caching, but those requests only
* count towards the GitHub rate limit if data has changes.</li>
* </ul>
*
* @author Liam Newman
*/
public class GHRateLimitTest extends AbstractGitHubWireMockTest {
public GHRateLimitTest() {
useDefaultGitHub = false;
}
@Override
protected WireMockConfiguration getWireMockOptions() {
return super.getWireMockOptions()
.extensions(ResponseTemplateTransformer.builder()
.global(true)
.maxCacheEntries(0L)
.build()
);
}
@Test
public void testGitHubRateLimit() throws Exception {
// Customized response that templates the date to keep things working
snapshotNotAllowed();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
Date lastReset = new Date(System.currentTimeMillis() / 1000L);
int lastRemaining = 5000;
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// /user gets response with rate limit information
gitHub = getGitHubBuilder()
.withEndpoint(mockGitHub.apiServer().baseUrl()).build();
gitHub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Since we already had rate limit info these don't request again
rateLimit = gitHub.lastRateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.getLimit(), equalTo(5000));
lastRemaining = rateLimit.getRemaining();
assertThat(rateLimit.getResetDate().compareTo(lastReset), greaterThanOrEqualTo(0));
lastReset = rateLimit.getResetDate();
GHRateLimit headerRateLimit = rateLimit;
// Give this a moment
Thread.sleep(1000);
// ratelimit() uses headerRateLimit if available and headerRateLimit is not expired
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = gitHub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.getLimit(), equalTo(5000));
// rate limit request is free
assertThat(rateLimit.remaining, equalTo(lastRemaining));
assertThat(rateLimit.getRemaining(), equalTo(lastRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), greaterThanOrEqualTo(0));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = gitHub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(3));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.getLimit(), equalTo(5000));
// rate limit request is free
assertThat(rateLimit.remaining, equalTo(lastRemaining));
assertThat(rateLimit.getRemaining(), equalTo(lastRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), greaterThanOrEqualTo(0));
gitHub.getOrganization(GITHUB_API_TEST_ORG);
assertThat(mockGitHub.getRequestCount(), equalTo(4));
assertThat(gitHub.lastRateLimit(), not(equalTo(headerRateLimit)));
rateLimit = gitHub.lastRateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.getLimit(), equalTo(5000));
// Org costs limit to query
assertThat(rateLimit.remaining, equalTo(lastRemaining - 1));
assertThat(rateLimit.getRemaining(), equalTo(lastRemaining - 1));
assertThat(rateLimit.getResetDate().compareTo(lastReset), greaterThanOrEqualTo(0));
lastReset = rateLimit.getResetDate();
headerRateLimit = rateLimit;
// ratelimit() should prefer headerRateLimit when it is most recent and not expired
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(4));
// Give this a moment
Thread.sleep(2000);
// Always requests new info
rateLimit = gitHub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(5));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.getLimit(), equalTo(5000));
// Org costs limit to query
assertThat(rateLimit.remaining, equalTo(lastRemaining - 1));
assertThat(rateLimit.getRemaining(), equalTo(lastRemaining - 1));
assertThat(rateLimit.getResetDate().compareTo(lastReset), greaterThanOrEqualTo(0));
// When getRateLimit() succeeds, headerRateLimit updates as usual as well (if needed)
// These are separate instances, but should be equal
assertThat(gitHub.rateLimit(), not(sameInstance(rateLimit)));
// Verify different record instances can be compared
assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore()));
// Verify different instances can be compared
// TODO: This is not work currently because the header rate limit has unknowns for records other than core.
// assertThat(gitHub.rateLimit().getCore(), equalTo(rateLimit.getCore()));
assertThat(gitHub.rateLimit(), not(sameInstance(headerRateLimit)));
assertThat(gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit()));
assertThat(mockGitHub.getRequestCount(), equalTo(5));
}
@Test
public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception {
// Customized response that results in file not found the same as GitHub Enterprise
snapshotNotAllowed();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
Date lastReset = new Date(System.currentTimeMillis() / 1000L);
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// Before any queries, rate limit starts as null but may be requested
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
assertThat(mockGitHub.getRequestCount(), equalTo(0));
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
rateLimit = gitHub.rateLimit();
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.limit, equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.remaining, equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
lastReset = rateLimit.getResetDate();
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// last is still null, because it actually means lastHeaderRateLimit
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// First call to /user gets response without rate limit information
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
gitHub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
rateLimit = gitHub.rateLimit();
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.limit, equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.remaining, equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
lastReset = rateLimit.getResetDate();
assertThat(mockGitHub.getRequestCount(), equalTo(3));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = gitHub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(4));
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.limit, equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.remaining, equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// last is still null, because it actually means lastHeaderRateLimit
assertThat(gitHub.lastRateLimit(), CoreMatchers.nullValue());
// ratelimit() tries not to make additional requests, uses queried rate limit since header not available
Thread.sleep(1000);
assertThat(gitHub.rateLimit(), sameInstance(rateLimit));
// -------------------------------------------------------------
// Second call to /user gets response with rate limit information
gitHub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
gitHub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(5));
// Since we already had rate limit info these don't request again
rateLimit = gitHub.lastRateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.getLimit(), equalTo(5000));
assertThat(rateLimit.remaining, equalTo(4978));
assertThat(rateLimit.getRemaining(), equalTo(4978));
assertThat(rateLimit.getResetDate().compareTo(lastReset), greaterThanOrEqualTo(0));
lastReset = rateLimit.getResetDate();
GHRateLimit headerRateLimit = rateLimit;
// Give this a moment
Thread.sleep(1000);
// ratelimit() uses headerRateLimit if available and headerRateLimit is not expired
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(5));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = gitHub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(6));
assertThat(rateLimit.getCore(), instanceOf(GHRateLimit.UnknownLimitRecord.class));
assertThat(rateLimit.limit, equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.getLimit(), equalTo(GHRateLimit.UnknownLimitRecord.unknownLimit));
assertThat(rateLimit.remaining, equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getRemaining(), equalTo(GHRateLimit.UnknownLimitRecord.unknownRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
// ratelimit() should prefer headerRateLimit when getRateLimit fails and headerRateLimit is not expired
assertThat(gitHub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(6));
// Wait for the header
Thread.sleep(1000);
}
// These tests should behave the same, showing server time adjustment working
@Test
public void testGitHubRateLimitExpirationServerFiveMinutesAhead() throws Exception {
executeExpirationTest();
}
@Test
public void testGitHubRateLimitExpirationServerFiveMinutesBehind() throws Exception {
executeExpirationTest();
}
private void executeExpirationTest() throws Exception {
// Customized response that templates the date to keep things working
snapshotNotAllowed();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
GHRateLimit headerRateLimit = null;
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// /user gets response with rate limit information
gitHub = getGitHubBuilder()
.withEndpoint(mockGitHub.apiServer().baseUrl()).build();
gitHub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Since we already had rate limit info these don't request again
headerRateLimit = gitHub.lastRateLimit();
rateLimit = gitHub.rateLimit();
assertThat(rateLimit, notNullValue());
assertThat("rateLimit() selects header instance when not expired, does not ask server",
rateLimit, sameInstance(headerRateLimit));
// Nothing changes still valid
Thread.sleep(1000);
assertThat("rateLimit() selects header instance when not expired, does not ask server",
gitHub.rateLimit(), sameInstance(headerRateLimit));
assertThat("rateLimit() selects header instance when not expired, does not ask server",
gitHub.lastRateLimit(), sameInstance(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// This time, rateLimit() should find an expired record and get a new one.
Thread.sleep(3000);
assertThat("Header instance has expired",
gitHub.lastRateLimit().isExpired(), is(true));
assertThat("rateLimit() will ask server when header instance expires and it has not called getRateLimit() yet",
gitHub.rateLimit(), not(sameInstance(rateLimit)));
assertThat("lastRateLimit() (header instance) is populated as part of internal call to getRateLimit()",
gitHub.lastRateLimit(), not(sameInstance(rateLimit)));
assertThat("After request, rateLimit() selects header instance since it has been refreshed",
gitHub.rateLimit(), sameInstance(gitHub.lastRateLimit()));
headerRateLimit = gitHub.lastRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
// This time, rateLimit() should find an expired header record, but a valid returned record
Thread.sleep(4000);
rateLimit = gitHub.rateLimit();
// Using custom data to have a header instance that expires before the queried instance
assertThat("if header instance expires but queried instance is valid, ratelimit() uses it without asking server",
gitHub.rateLimit(), not(sameInstance(gitHub.lastRateLimit())));
assertThat("ratelimit() should almost never return a return a GHRateLimit that is already expired",
gitHub.rateLimit().isExpired(), is(false));
assertThat("Header instance hasn't been reloaded",
gitHub.lastRateLimit(), sameInstance(headerRateLimit));
assertThat("Header instance has expired",
gitHub.lastRateLimit().isExpired(), is(true));
assertThat(mockGitHub.getRequestCount(), equalTo(2));
// Finally they both expire and rateLimit() should find both expired and get a new record
Thread.sleep(2000);
headerRateLimit = gitHub.rateLimit();
assertThat("rateLimit() has asked server for new information",
gitHub.rateLimit(), not(sameInstance(rateLimit)));
assertThat("rateLimit() has asked server for new information",
gitHub.lastRateLimit(), not(sameInstance(rateLimit)));
assertThat("rateLimit() selects header instance when not expired, does not ask server",
gitHub.rateLimit(), sameInstance((gitHub.lastRateLimit())));
assertThat(mockGitHub.getRequestCount(), equalTo(3));
}
private static GHRepository getRepository(GitHub gitHub) throws IOException {
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
}
}

View File

@@ -6,8 +6,6 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import static org.hamcrest.CoreMatchers.*;
/**
* Unit test for {@link GitHub}.
*/
@@ -97,233 +95,6 @@ public class GitHubConnectionTest extends AbstractGitHubWireMockTest {
assertEquals("",github.login);
}
@Test
public void testGitHubRateLimit() throws Exception {
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
GitHub hub = null;
Date lastReset = new Date(System.currentTimeMillis() / 1000L);
int lastRemaining = 5000;
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// /user gets response with rate limit information
hub = getGitHubBuilder()
.withEndpoint(mockGitHub.apiServer().baseUrl()).build();
hub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Since we already had rate limit info these don't request again
rateLimit = hub.lastRateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
lastRemaining = rateLimit.remaining;
// Because we're gettting this from old mocked info, it will be an older date
//assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(-1));
lastReset = rateLimit.getResetDate();
GHRateLimit headerRateLimit = rateLimit;
// Give this a moment
Thread.sleep(1000);
// ratelimit() uses headerRateLimit if available
assertThat(hub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = hub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
// rate limit request is free
assertThat(rateLimit.remaining, equalTo(lastRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = hub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(3));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
// rate limit request is free
assertThat(rateLimit.remaining, equalTo(lastRemaining));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
hub.getOrganization(GITHUB_API_TEST_ORG);
assertThat(mockGitHub.getRequestCount(), equalTo(4));
assertThat(hub.lastRateLimit(), not(equalTo(headerRateLimit)));
rateLimit = hub.lastRateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
// Org costs limit to query
assertThat(rateLimit.remaining, equalTo(lastRemaining - 1));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
lastReset = rateLimit.getResetDate();
headerRateLimit = rateLimit;
// ratelimit() should prefer headerRateLimit when it is most recent
assertThat(hub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(4));
// Always requests new info
rateLimit = hub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(5));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
// Org costs limit to query
assertThat(rateLimit.remaining, equalTo(lastRemaining - 1));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(0));
// ratelimit() should prefer headerRateLimit when getRateLimit() fails
// BUG: When getRateLimit() succeeds, it should reset the ratelimit() to the new value
// assertThat(hub.rateLimit(), equalTo(rateLimit));
// assertThat(hub.rateLimit(), not(equalTo(headerRateLimit)));
assertThat(hub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(5));
}
@Test
public void testGitHubEnterpriseDoesNotHaveRateLimit() throws Exception {
// Customized response that results in file not found the same as GitHub Enterprise
snapshotNotAllowed();
assertThat(mockGitHub.getRequestCount(), equalTo(0));
GHRateLimit rateLimit = null;
GitHub hub = null;
Date lastReset = new Date(System.currentTimeMillis() / 1000L);
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// Before any queries, rate limit starts as null but may be requested
hub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
assertThat(mockGitHub.getRequestCount(), equalTo(0));
assertThat(hub.lastRateLimit(), nullValue());
rateLimit = hub.rateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(1000000));
assertThat(rateLimit.remaining, equalTo(1000000));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
lastReset = rateLimit.getResetDate();
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// last is still null, because it actually means lastHeaderRateLimit
assertThat(hub.lastRateLimit(), nullValue());
assertThat(mockGitHub.getRequestCount(), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// -------------------------------------------------------------
// First call to /user gets response without rate limit information
hub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
hub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(2));
assertThat(hub.lastRateLimit(), nullValue());
rateLimit = hub.rateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(1000000));
assertThat(rateLimit.remaining, equalTo(1000000));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
lastReset = rateLimit.getResetDate();
assertThat(mockGitHub.getRequestCount(), equalTo(3));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = hub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(4));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(1000000));
assertThat(rateLimit.remaining, equalTo(1000000));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// last is still null, because it actually means lastHeaderRateLimit
assertThat(hub.lastRateLimit(), nullValue());
// ratelimit() tries not to make additional requests, uses queried rate limit since header not available
Thread.sleep(1000);
assertThat(hub.rateLimit(), equalTo(rateLimit));
// -------------------------------------------------------------
// Second call to /user gets response with rate limit information
hub = GitHub.connectToEnterprise(mockGitHub.apiServer().baseUrl(), "bogus", "bogus");
hub.getMyself();
assertThat(mockGitHub.getRequestCount(), equalTo(5));
// Since we already had rate limit info these don't request again
rateLimit = hub.lastRateLimit();
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(5000));
assertThat(rateLimit.remaining, equalTo(4978));
// Because we're gettting this from old mocked info, it will be an older date
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(-1));
lastReset = rateLimit.getResetDate();
GHRateLimit headerRateLimit = rateLimit;
// Give this a moment
Thread.sleep(1000);
// ratelimit() uses headerRateLimit if available
assertThat(hub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(5));
// Give this a moment
Thread.sleep(1000);
// Always requests new info
rateLimit = hub.getRateLimit();
assertThat(mockGitHub.getRequestCount(), equalTo(6));
assertThat(rateLimit, notNullValue());
assertThat(rateLimit.limit, equalTo(1000000));
assertThat(rateLimit.remaining, equalTo(1000000));
assertThat(rateLimit.getResetDate().compareTo(lastReset), equalTo(1));
// Give this a moment
Thread.sleep(1000);
// ratelimit() should prefer headerRateLimit when getRateLimit fails
assertThat(hub.rateLimit(), equalTo(headerRateLimit));
assertThat(mockGitHub.getRequestCount(), equalTo(6));
}
@Test
public void testGitHubIsApiUrlValid() throws IOException {
GitHub hub = GitHub.connectAnonymously();

View File

@@ -1,5 +1,7 @@
package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.eclipse.jgit.api.Git;
import org.junit.Assert;
import org.junit.Test;
@@ -11,6 +13,7 @@ import java.util.TimeZone;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.core.Is.is;
/**
* Unit test for {@link GitHub} static helpers.
@@ -68,6 +71,65 @@ public class GitHubStaticTest extends Assert {
} catch (IllegalStateException e) {
assertThat(e.getMessage(), equalTo("Unable to parse the timestamp: " + instantBadFormat));
}
}
@Test
public void testGitHubRateLimitShouldReplaceRateLimit() throws Exception {
GHRateLimit.Record unknown0 = GHRateLimit.Unknown().getCore();
GHRateLimit.Record unknown1 = GHRateLimit.Unknown().getCore();
GHRateLimit.Record record0 = new GHRateLimit.Record(10, 10, 10L);
GHRateLimit.Record record1 = new GHRateLimit.Record(10, 9, 10L);
GHRateLimit.Record record2 = new GHRateLimit.Record(10, 2, 10L);
GHRateLimit.Record record3 = new GHRateLimit.Record(10, 10, 20L);
GHRateLimit.Record record4 = new GHRateLimit.Record(10, 5, 20L);
Thread.sleep(2000);
GHRateLimit.Record recordWorst = new GHRateLimit.Record(Integer.MAX_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE);
GHRateLimit.Record record00 = new GHRateLimit.Record(10, 10, 10L);
GHRateLimit.Record unknown2 = GHRateLimit.Unknown().getCore();
// Rate-limit records maybe created and returned in different orders.
// We should update to the regular records over unknowns.
// After that, we should update to the candidate if its limit is lower or its reset is later.
assertThat("Equivalent unknown should not replace", GitHub.shouldReplace(unknown0, unknown1), is(false));
assertThat("Equivalent unknown should not replace", GitHub.shouldReplace(unknown1, unknown0), is(false));
assertThat("Later unknown should replace earlier", GitHub.shouldReplace(unknown2, unknown0), is(true));
assertThat("Earlier unknown should not replace later", GitHub.shouldReplace(unknown0, unknown2), is(false));
assertThat("Worst record should replace later unknown", GitHub.shouldReplace(recordWorst, unknown1), is(true));
assertThat("Unknown should not replace worst record", GitHub.shouldReplace(unknown1, recordWorst), is(false));
assertThat("Earlier record should replace later worst", GitHub.shouldReplace(record0, recordWorst), is(true));
assertThat("Later worst record should not replace earlier", GitHub.shouldReplace(recordWorst, record0), is(false));
assertThat("Equivalent record should not replace", GitHub.shouldReplace(record0, record00), is(false));
assertThat("Equivalent record should not replace", GitHub.shouldReplace(record00, record0), is(false));
assertThat("Lower limit record should replace higher", GitHub.shouldReplace(record1, record0), is(true));
assertThat("Lower limit record should replace higher", GitHub.shouldReplace(record2, record1), is(true));
assertThat("Higher limit record should not replace lower", GitHub.shouldReplace(record1, record2), is(false));
assertThat("Higher limit record with later reset should replace lower", GitHub.shouldReplace(record3, record2), is(true));
assertThat("Lower limit record with later reset should replace higher", GitHub.shouldReplace(record4, record1), is(true));
assertThat("Lower limit record with earlier reset should not replace higher", GitHub.shouldReplace(record2, record4), is(false));
}
static String formatDate(Date dt, String format) {

View File

@@ -8,6 +8,7 @@ import com.squareup.okhttp.Cache;
import com.squareup.okhttp.OkHttpClient;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.kohsuke.github.*;
@@ -185,6 +186,7 @@ public class OkHttpConnectorTest extends AbstractGitHubWireMockTest {
assertThat("getHitCount", cache.getHitCount(), is(maxAgeThreeHitCount));
}
@Ignore("ISSUE #597 - Correctly formatted Last-Modified headers cause this test to fail")
@Test
public void OkHttpConnector_Cache_MaxAgeDefault_Zero() throws Exception {
// The responses were recorded from github, but the Date headers

View File

@@ -9,13 +9,10 @@
"status": 404,
"body": "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3\"}",
"headers": {
"Date": "Mon, 07 Oct 2019 17:05:48 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "404 Not Found",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4976",
"X-RateLimit-Reset": "1570470839",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "unknown, github.v3",

View File

@@ -9,7 +9,7 @@
"status": 200,
"bodyFileName": "user-a0bafdae-2f0d-4d0b-966b-f2408c1240bd.json",
"headers": {
"Date": "Mon, 07 Oct 2019 17:05:48 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "user-a0bafdae-2f0d-4d0b-966b-f2408c1240bd.json",
"headers": {
"Date": "Mon, 07 Oct 2019 17:05:48 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4978",
"X-RateLimit-Reset": "1570470839",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -3,22 +3,22 @@
"core": {
"limit": 5000,
"remaining": 4896,
"reset": 1570478899
"reset": {{now offset='1 hours' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": 1570478180
"reset": {{now offset='1 hours' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": 1570481642
"reset": {{now offset='1 hours' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": 1570481642
"reset": {{now offset='1 hours' format='unix'}}
}
},
"rate": {

View File

@@ -3,22 +3,22 @@
"core": {
"limit": 5000,
"remaining": 4897,
"reset": 1570478899
"reset": {{now offset='1 hours' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": 1570478180
"reset": {{now offset='1 hours' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": 1570481642
"reset": {{now offset='1 hours' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": 1570481642
"reset": {{now offset='1 hours' format='unix'}}
}
},
"rate": {

View File

@@ -3,22 +3,22 @@
"core": {
"limit": 5000,
"remaining": 4897,
"reset": 1570478899
"reset": {{now offset='1 hours' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": 1570478180
"reset": {{now offset='1 hours' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": 1570481642
"reset": {{now offset='1 hours' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": 1570481642
"reset": {{now offset='1 hours' format='unix'}}
}
},
"rate": {

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "orgs_github-api-test-org-7ea37b72-8b01-4ed5-a63d-98d32b2faa0f.json",
"headers": {
"Date": "Mon, 07 Oct 2019 19:55:56 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4896",
"X-RateLimit-Reset": "1570478899",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "rate_limit-f22b1cea-3679-481d-8b95-459b2c47bf98.json",
"headers": {
"Date": "Mon, 07 Oct 2019 19:55:54 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "1570478899",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "rate_limit-a1f82a96-500c-4462-ae7b-e0159afa8208.json",
"headers": {
"Date": "Mon, 07 Oct 2019 19:55:55 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "1570478899",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "rate_limit-4e2fc33b-fb25-4dfc-9d56-34f9b4d707be.json",
"headers": {
"Date": "Mon, 07 Oct 2019 19:55:56 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4896",
"X-RateLimit-Reset": "1570478899",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "user-a460fd69-99f3-46c7-aeb1-888c34085d4a.json",
"headers": {
"Date": "Mon, 07 Oct 2019 19:55:52 GMT",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "1570478899",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -0,0 +1,29 @@
{
"resources": {
"core": {
"limit": 5000,
"remaining": 4896,
"reset": {{now offset='305 seconds' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": {{now offset='305 seconds' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='305 seconds' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='305 seconds' format='unix'}}
}
},
"rate": {
"limit": 5000,
"remaining": 4896,
"reset": 1570478899
}
}

View File

@@ -0,0 +1,29 @@
{
"resources": {
"core": {
"limit": 5000,
"remaining": 4897,
"reset": {{now offset='305 seconds' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": {{now offset='305 seconds' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='305 seconds' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='305 seconds' format='unix'}}
}
},
"rate": {
"limit": 5000,
"remaining": 4897,
"reset": 1570478899
}
}

View File

@@ -0,0 +1,29 @@
{
"resources": {
"core": {
"limit": 5000,
"remaining": 4897,
"reset": {{now offset='305 seconds' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": {{now offset='305 seconds' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='305 seconds' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='305 seconds' format='unix'}}
}
},
"rate": {
"limit": 5000,
"remaining": 4897,
"reset": 1570478899
}
}

View File

@@ -0,0 +1,45 @@
{
"login": "bitwiseman",
"id": 1958953,
"node_id": "MDQ6VXNlcjE5NTg5NTM=",
"avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/bitwiseman",
"html_url": "https://github.com/bitwiseman",
"followers_url": "https://api.github.com/users/bitwiseman/followers",
"following_url": "https://api.github.com/users/bitwiseman/following{/other_user}",
"gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}",
"starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions",
"organizations_url": "https://api.github.com/users/bitwiseman/orgs",
"repos_url": "https://api.github.com/users/bitwiseman/repos",
"events_url": "https://api.github.com/users/bitwiseman/events{/privacy}",
"received_events_url": "https://api.github.com/users/bitwiseman/received_events",
"type": "User",
"site_admin": false,
"name": "Liam Newman",
"company": "Cloudbees, Inc.",
"blog": "",
"location": "Seattle, WA, USA",
"email": "bitwiseman@gmail.com",
"hireable": null,
"bio": "https://twitter.com/bitwiseman",
"public_repos": 168,
"public_gists": 4,
"followers": 136,
"following": 9,
"created_at": "2012-07-11T20:38:33Z",
"updated_at": "2019-09-24T19:32:29Z",
"private_gists": 7,
"total_private_repos": 9,
"owned_private_repos": 0,
"disk_usage": 33697,
"collaborators": 0,
"two_factor_authentication": true,
"plan": {
"name": "free",
"space": 976562499,
"collaborators": 0,
"private_repos": 10000
}
}

View File

@@ -0,0 +1,41 @@
{
"id": "f22b1cea-3679-481d-8b95-459b2c47bf98",
"name": "rate_limit",
"request": {
"url": "/rate_limit",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "rate_limit-f22b1cea-3679-481d-8b95-459b2c47bf98.json",
"headers": {
"Date": "{{now offset='5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "{{now offset='303 seconds' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding",
"X-GitHub-Request-Id": "E39F:3620:2D9D346:369B91A:5D9B9848"
}
},
"uuid": "f22b1cea-3679-481d-8b95-459b2c47bf98",
"persistent": true,
"scenarioName": "scenario-1-rate_limit",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-rate_limit-2",
"insertionIndex": 2
}

View File

@@ -0,0 +1,41 @@
{
"id": "a1f82a96-500c-4462-ae7b-e0159afa8208",
"name": "rate_limit",
"request": {
"url": "/rate_limit",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "rate_limit-a1f82a96-500c-4462-ae7b-e0159afa8208.json",
"headers": {
"Date": "{{now offset='5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "{{now offset='303 seconds' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding",
"X-GitHub-Request-Id": "E39F:3620:2D9D3B3:369BA1D:5D9B984A"
}
},
"uuid": "a1f82a96-500c-4462-ae7b-e0159afa8208",
"persistent": true,
"scenarioName": "scenario-1-rate_limit",
"requiredScenarioState": "scenario-1-rate_limit-2",
"newScenarioState": "scenario-1-rate_limit-3",
"insertionIndex": 3
}

View File

@@ -0,0 +1,40 @@
{
"id": "4e2fc33b-fb25-4dfc-9d56-34f9b4d707be",
"name": "rate_limit",
"request": {
"url": "/rate_limit",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "rate_limit-4e2fc33b-fb25-4dfc-9d56-34f9b4d707be.json",
"headers": {
"Date": "{{now offset='5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4896",
"X-RateLimit-Reset": "{{now offset='303 seconds' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding",
"X-GitHub-Request-Id": "E39F:3620:2D9D3D1:369BAD1:5D9B984C"
}
},
"uuid": "4e2fc33b-fb25-4dfc-9d56-34f9b4d707be",
"persistent": true,
"scenarioName": "scenario-1-rate_limit",
"requiredScenarioState": "scenario-1-rate_limit-3",
"insertionIndex": 5
}

View File

@@ -0,0 +1,43 @@
{
"id": "a460fd69-99f3-46c7-aeb1-888c34085d4a",
"name": "user",
"request": {
"url": "/user",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "user-a460fd69-99f3-46c7-aeb1-888c34085d4a.json",
"headers": {
"Date": "{{now offset='5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "{{now offset='303 seconds' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"af0c41afcacb8ceee14b7d896719c3bd\"",
"Last-Modified": "Tue, 24 Sep 2019 19:32:29 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "E39F:3620:2D9D266:369B909:5D9B9848"
}
},
"uuid": "a460fd69-99f3-46c7-aeb1-888c34085d4a",
"persistent": true,
"insertionIndex": 1
}

View File

@@ -0,0 +1,29 @@
{
"resources": {
"core": {
"limit": 5000,
"remaining": 4896,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='-295 seconds' format='unix'}}
}
},
"rate": {
"limit": 5000,
"remaining": 4896,
"reset": 1570478899
}
}

View File

@@ -0,0 +1,29 @@
{
"resources": {
"core": {
"limit": 5000,
"remaining": 4897,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='-295 seconds' format='unix'}}
}
},
"rate": {
"limit": 5000,
"remaining": 4897,
"reset": 1570478899
}
}

View File

@@ -0,0 +1,29 @@
{
"resources": {
"core": {
"limit": 5000,
"remaining": 4897,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"search": {
"limit": 30,
"remaining": 30,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"graphql": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='-295 seconds' format='unix'}}
},
"integration_manifest": {
"limit": 5000,
"remaining": 5000,
"reset": {{now offset='-295 seconds' format='unix'}}
}
},
"rate": {
"limit": 5000,
"remaining": 4897,
"reset": 1570478899
}
}

View File

@@ -0,0 +1,45 @@
{
"login": "bitwiseman",
"id": 1958953,
"node_id": "MDQ6VXNlcjE5NTg5NTM=",
"avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/bitwiseman",
"html_url": "https://github.com/bitwiseman",
"followers_url": "https://api.github.com/users/bitwiseman/followers",
"following_url": "https://api.github.com/users/bitwiseman/following{/other_user}",
"gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}",
"starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions",
"organizations_url": "https://api.github.com/users/bitwiseman/orgs",
"repos_url": "https://api.github.com/users/bitwiseman/repos",
"events_url": "https://api.github.com/users/bitwiseman/events{/privacy}",
"received_events_url": "https://api.github.com/users/bitwiseman/received_events",
"type": "User",
"site_admin": false,
"name": "Liam Newman",
"company": "Cloudbees, Inc.",
"blog": "",
"location": "Seattle, WA, USA",
"email": "bitwiseman@gmail.com",
"hireable": null,
"bio": "https://twitter.com/bitwiseman",
"public_repos": 168,
"public_gists": 4,
"followers": 136,
"following": 9,
"created_at": "2012-07-11T20:38:33Z",
"updated_at": "2019-09-24T19:32:29Z",
"private_gists": 7,
"total_private_repos": 9,
"owned_private_repos": 0,
"disk_usage": 33697,
"collaborators": 0,
"two_factor_authentication": true,
"plan": {
"name": "free",
"space": 976562499,
"collaborators": 0,
"private_repos": 10000
}
}

View File

@@ -0,0 +1,41 @@
{
"id": "f22b1cea-3679-481d-8b95-459b2c47bf98",
"name": "rate_limit",
"request": {
"url": "/rate_limit",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "rate_limit-f22b1cea-3679-481d-8b95-459b2c47bf98.json",
"headers": {
"Date": "{{now offset='-5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "{{now offset='-297 seconds' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding",
"X-GitHub-Request-Id": "E39F:3620:2D9D346:369B91A:5D9B9848"
}
},
"uuid": "f22b1cea-3679-481d-8b95-459b2c47bf98",
"persistent": true,
"scenarioName": "scenario-1-rate_limit",
"requiredScenarioState": "Started",
"newScenarioState": "scenario-1-rate_limit-2",
"insertionIndex": 2
}

View File

@@ -0,0 +1,41 @@
{
"id": "a1f82a96-500c-4462-ae7b-e0159afa8208",
"name": "rate_limit",
"request": {
"url": "/rate_limit",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "rate_limit-a1f82a96-500c-4462-ae7b-e0159afa8208.json",
"headers": {
"Date": "{{now offset='-5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "{{now offset='-297 seconds' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding",
"X-GitHub-Request-Id": "E39F:3620:2D9D3B3:369BA1D:5D9B984A"
}
},
"uuid": "a1f82a96-500c-4462-ae7b-e0159afa8208",
"persistent": true,
"scenarioName": "scenario-1-rate_limit",
"requiredScenarioState": "scenario-1-rate_limit-2",
"newScenarioState": "scenario-1-rate_limit-3",
"insertionIndex": 3
}

View File

@@ -0,0 +1,40 @@
{
"id": "4e2fc33b-fb25-4dfc-9d56-34f9b4d707be",
"name": "rate_limit",
"request": {
"url": "/rate_limit",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "rate_limit-4e2fc33b-fb25-4dfc-9d56-34f9b4d707be.json",
"headers": {
"Date": "{{now offset='-5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4896",
"X-RateLimit-Reset": "{{now offset='-297 seconds' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"Vary": "Accept-Encoding",
"X-GitHub-Request-Id": "E39F:3620:2D9D3D1:369BAD1:5D9B984C"
}
},
"uuid": "4e2fc33b-fb25-4dfc-9d56-34f9b4d707be",
"persistent": true,
"scenarioName": "scenario-1-rate_limit",
"requiredScenarioState": "scenario-1-rate_limit-3",
"insertionIndex": 5
}

View File

@@ -0,0 +1,43 @@
{
"id": "a460fd69-99f3-46c7-aeb1-888c34085d4a",
"name": "user",
"request": {
"url": "/user",
"method": "GET"
},
"response": {
"status": 200,
"bodyFileName": "user-a460fd69-99f3-46c7-aeb1-888c34085d4a.json",
"headers": {
"Date": "{{now offset='-5 minutes' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4897",
"X-RateLimit-Reset": "{{now offset='-297 seconds' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"af0c41afcacb8ceee14b7d896719c3bd\"",
"Last-Modified": "Tue, 24 Sep 2019 19:32:29 GMT",
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
"X-Accepted-OAuth-Scopes": "",
"X-GitHub-Media-Type": "unknown, github.v3",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy": "default-src 'none'",
"X-GitHub-Request-Id": "E39F:3620:2D9D266:369B909:5D9B9848"
}
},
"uuid": "a460fd69-99f3-46c7-aeb1-888c34085d4a",
"persistent": true,
"insertionIndex": 1
}

View File

@@ -24,6 +24,6 @@
"rate": {
"limit": 5000,
"remaining": 4717,
"reset": 1569866107
"reset": {{now offset='1 hours' format='unix'}}
}
}

View File

@@ -24,6 +24,6 @@
"rate": {
"limit": 5000,
"remaining": 4970,
"reset": 1569875630
"reset": {{now offset='1 hours' format='unix'}}
}
}

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "orgs_github-api-test-org-ec2931f3-a8cd-4482-a866-aca52276d270.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4969",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "rate_limit-36588a64-cb68-4ea5-8995-c2cdced6b84a.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4970",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "",

View File

@@ -9,20 +9,20 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-5432b23c-70f2-4ecf-a380-a232afeef015.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4966",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"4f508593b64df214fee8f6ab42df633c\"",
"Last-Modified": "{{now offset='-1 seconds'}}",
"Last-Modified": "{{now offset='-1 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "github.v3; format=json",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4966",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"4f508593b64df214fee8f6ab42df633c\"",
"Last-Modified": "{{now offset='-1 seconds'}}",
"Last-Modified": "{{now offset='-1 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4966",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"4f508593b64df214fee8f6ab42df633c\"",
"Last-Modified": "{{now offset='-2 seconds'}}",
"Last-Modified": "{{now offset='-2 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4966",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"4f508593b64df214fee8f6ab42df633c\"",
"Last-Modified": "{{now offset='-3 seconds'}}",
"Last-Modified": "{{now offset='-3 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -14,20 +14,20 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-7e396e4c-c5eb-4bc0-a5cd-3d9f75fac0f0.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4963",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"31f73a7ecc35bbecec125851ce166af4\"",
"Last-Modified": "{{now offset='-7 seconds'}}",
"Last-Modified": "{{now offset='-7 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "github.v3; format=json",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4963",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"31f73a7ecc35bbecec125851ce166af4\"",
"Last-Modified": "{{now offset='-10 seconds'}}",
"Last-Modified": "{{now offset='-10 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4963",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"31f73a7ecc35bbecec125851ce166af4\"",
"Last-Modified": "{{now offset='-15 seconds'}}",
"Last-Modified": "{{now offset='-15 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4963",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"31f73a7ecc35bbecec125851ce166af4\"",
"Last-Modified": "{{now offset='-20 seconds'}}",
"Last-Modified": "{{now offset='-20 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -9,20 +9,20 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-b99f84bd-4eaa-4aeb-8f1c-ba64e617d15f.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4968",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"295ae3430c604f3d10b6eb145fe511b5\"",
"Last-Modified": "{{now offset='-1 seconds'}}",
"Last-Modified": "{{now offset='-1 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "github.v3; format=json",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4968",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"295ae3430c604f3d10b6eb145fe511b5\"",
"Last-Modified": "{{now offset='-1 seconds'}}",
"Last-Modified": "{{now offset='-1 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4968",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"295ae3430c604f3d10b6eb145fe511b5\"",
"Last-Modified": "{{now offset='-2 seconds'}}",
"Last-Modified": "{{now offset='-2 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -13,19 +13,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4968",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"295ae3430c604f3d10b6eb145fe511b5\"",
"Last-Modified": "{{now offset='-3 seconds'}}",
"Last-Modified": "{{now offset='-3 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -8,19 +8,19 @@
"response": {
"status": 304,
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Server": "GitHub.com",
"Status": "304 Not Modified",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4968",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "\"295ae3430c604f3d10b6eb145fe511b5\"",
"Last-Modified": "{{now offset='-7 seconds'}}",
"Last-Modified": "{{now offset='-7 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin": "*",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",

View File

@@ -16,13 +16,13 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-0db05723-d8ab-412d-bcaf-fa416eb44138.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4967",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "user-5dbb2b95-e55b-4185-b143-ec3f21495fa6.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4970",
"X-RateLimit-Reset": "1569875630",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -24,6 +24,6 @@
"rate": {
"limit": 5000,
"remaining": 4984,
"reset": 1569872730
"reset": {{now offset='1 hours' format='unix'}}
}
}

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "orgs_github-api-test-org-1d3815b7-1441-4a5d-a4eb-ffa13c700503.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4983",
"X-RateLimit-Reset": "1569872730",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "rate_limit-b23929e7-7216-403a-b1c4-1b0c2ecf6f85.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4984",
"X-RateLimit-Reset": "1569872730",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "",

View File

@@ -9,20 +9,20 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-ec57e178-8204-439b-b45a-58c773fa46f6.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4982",
"X-RateLimit-Reset": "1569872730",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"ef30773e1dfdb07ba4ea64143df970ab\"",
"Last-Modified": "{{now offset='-20 seconds'}}",
"Last-Modified": "{{now offset='-20 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "github.v3; format=json",

View File

@@ -16,13 +16,13 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-82db6373-85d8-4fc9-97c7-b98612c52402.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4981",
"X-RateLimit-Reset": "1569872730",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -9,20 +9,20 @@
"status": 200,
"bodyFileName": "repos_github-api-test-org_github-api-02d53c37-9838-422a-9184-a08ef1487126.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4980",
"X-RateLimit-Reset": "1569872730",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",
"Accept-Encoding"
],
"ETag": "W/\"ff0623de72a672b583e0f4473a5bb57c\"",
"Last-Modified": "{{now offset='-20 seconds'}}",
"Last-Modified": "{{now offset='-20 seconds' timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "repo",
"X-GitHub-Media-Type": "github.v3; format=json",

View File

@@ -9,13 +9,13 @@
"status": 200,
"bodyFileName": "user-d958863f-a1fb-4a46-bc43-aeccd3eef451.json",
"headers": {
"Date": "{{now}}",
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
"Content-Type": "application/json; charset=utf-8",
"Server": "GitHub.com",
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4984",
"X-RateLimit-Reset": "1569872730",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -24,6 +24,6 @@
"rate": {
"limit": 5000,
"remaining": 4995,
"reset": 1569867392
"reset": {{now offset='1 hours' format='unix'}}
}
}

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4994",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4995",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "no-cache",
"X-OAuth-Scopes": "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
"X-Accepted-OAuth-Scopes": "",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4987",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4986",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4985",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4984",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4981",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4980",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4979",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4978",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4993",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4992",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4991",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4990",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4989",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -22,7 +22,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4988",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",

View File

@@ -15,7 +15,7 @@
"Status": "200 OK",
"X-RateLimit-Limit": "5000",
"X-RateLimit-Remaining": "4995",
"X-RateLimit-Reset": "1569867392",
"X-RateLimit-Reset": "{{now offset='1 hours' format='unix'}}",
"Cache-Control": "private, max-age=60, s-maxage=60",
"Vary": [
"Accept, Authorization, Cookie, X-GitHub-OTP",