Skip to content

Commit 839f096

Browse files
committed
Add caching error test for OkHttp
1 parent f3b2fdc commit 839f096

26 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package org.kohsuke.github.extras;
2+
3+
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
4+
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
5+
import com.squareup.okhttp.Cache;
6+
import com.squareup.okhttp.OkHttpClient;
7+
import com.squareup.okhttp.OkUrlFactory;
8+
import org.apache.commons.io.FileUtils;
9+
import org.junit.Before;
10+
import org.junit.Ignore;
11+
import org.junit.Test;
12+
import org.kohsuke.github.AbstractGitHubWireMockTest;
13+
import org.kohsuke.github.GHContent;
14+
import org.kohsuke.github.GHException;
15+
import org.kohsuke.github.GHFileNotFoundException;
16+
import org.kohsuke.github.GHIssueState;
17+
import org.kohsuke.github.GHPullRequest;
18+
import org.kohsuke.github.GHRef;
19+
import org.kohsuke.github.GHRepository;
20+
import org.kohsuke.github.GitHub;
21+
22+
import java.io.File;
23+
import java.io.IOException;
24+
import java.util.List;
25+
26+
import static org.hamcrest.core.Is.is;
27+
28+
/**
29+
* Test showing the behavior of OkHttpConnector cache with GitHub 404 responses.
30+
*
31+
* @author Liam Newman
32+
*/
33+
public class GitHubCachingTest extends AbstractGitHubWireMockTest {
34+
35+
public GitHubCachingTest() {
36+
useDefaultGitHub = false;
37+
}
38+
39+
String testRefName = "heads/test/content_ref_cache";
40+
41+
@Override
42+
protected WireMockConfiguration getWireMockOptions() {
43+
return super.getWireMockOptions()
44+
.extensions(ResponseTemplateTransformer.builder().global(true).maxCacheEntries(0L).build());
45+
}
46+
47+
@Before
48+
public void setupRepo() throws Exception {
49+
if (mockGitHub.isUseProxy()) {
50+
for (GHPullRequest pr : getRepository(this.gitHubBeforeAfter).getPullRequests(GHIssueState.OPEN)) {
51+
pr.close();
52+
}
53+
try {
54+
GHRef ref = getRepository(this.gitHubBeforeAfter).getRef(testRefName);
55+
ref.delete();
56+
} catch (IOException e) {
57+
}
58+
}
59+
}
60+
61+
@Test
62+
public void OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error_runnable() throws Exception {
63+
64+
requireProxy("This test method can be run locally for debugging and analyzing.");
65+
OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error();
66+
}
67+
68+
@Test
69+
public void OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error() throws Exception {
70+
// ISSUE #669
71+
// requireProxy("For clarity. Will switch to snapshot shortly.");
72+
// snapshotNotAllowed();
73+
74+
OkHttpClient client = createClient(true);
75+
OkHttpConnector connector = new OkHttpConnector(new OkUrlFactory(client));
76+
77+
this.gitHub = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl())
78+
.withConnector(connector)
79+
.build();
80+
81+
// Alternate client also doing caching but staying in a good state
82+
// We use this to do sanity checks and other information gathering
83+
GitHub gitHub2 = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl())
84+
.withConnector(new OkHttpConnector(new OkUrlFactory(createClient(true))))
85+
.build();
86+
87+
// Create a branch from a known conflicting branch
88+
GHRepository repo = getRepository(gitHub);
89+
90+
String baseSha = repo.getRef("heads/test/unmergeable").getObject().getSha();
91+
92+
GHRef ref;
93+
ref = repo.createRef("refs/" + testRefName, baseSha);
94+
95+
// Verify we can query the created ref
96+
ref = repo.getRef(testRefName);
97+
98+
// Verify we can query the created ref from cache
99+
ref = repo.getRef(testRefName);
100+
101+
// Delete the ref
102+
ref.delete();
103+
104+
// This is just to show this isn't a race condition
105+
Thread.sleep(2000);
106+
107+
// Try to get the non-existant ref (GHFileNotFound)
108+
try {
109+
repo.getRef(testRefName);
110+
fail();
111+
} catch (GHFileNotFoundException e) {
112+
// expected
113+
114+
// FYI: Querying again when the item is actually not present does not produce a 304
115+
// It produces another 404,
116+
// Try to get the non-existant ref (GHFileNotFound)
117+
try {
118+
repo.getRef(testRefName);
119+
fail();
120+
} catch (GHFileNotFoundException ex) {
121+
// expected
122+
}
123+
124+
}
125+
126+
// This is just to show this isn't a race condition
127+
Thread.sleep(2000);
128+
129+
ref = repo.createRef("refs/" + testRefName, baseSha);
130+
131+
// Verify ref exists and can be queried from uncached connection
132+
// Expected: success
133+
// Actual: still GHFileNotFound due to caching: GitHub incorrectly returns 304
134+
// even though contents of the ref have changed.
135+
//
136+
// There source of this issue seems to be that 404's do not return an ETAG,
137+
// so the cache falls back to using "If-Modified-Since" which is erroneously returns a 304.
138+
//
139+
// NOTE: This is even worse than you might think: 404 responses don't return an ETAG, but 304 responses do.
140+
//
141+
// Due erroneous 304 returned from "If-Modified-Since", the ETAG returned by the first 304
142+
// is actually the ETAG for the NEW state of the ref query (the one where the ref exists).
143+
// This can be verified by comparing the ETAG from gitHub2 client to the ETAG in error.
144+
//
145+
// This means that server thinks it telling the client that the new state is stable
146+
// while the cache thinks it confirming the old state hasn't changed.
147+
//
148+
// So, after the first 304, the failure is locked in via ETAG and won't until the ref is modified again
149+
// or until the cache ages out entry without the URL being requeried (which is why users report that refreshing
150+
// is now help).
151+
152+
try {
153+
repo.getRef(testRefName);
154+
} catch (GHFileNotFoundException e) {
155+
// Sanity check: ref exists and can be queried from other client
156+
getRepository(gitHub2).getRef(testRefName);
157+
158+
// We're going to fail, query again to see the incorrect ETAG cached from first query being used
159+
// It is the same ETAG as the one returned to the second client.
160+
// Now we're in trouble.
161+
repo.getRef(testRefName);
162+
163+
// We should never fail the first query and pass the second,
164+
// the test has still failed if it get here.
165+
fail();
166+
}
167+
168+
// OMG, the workaround succeeded!
169+
// This correct response should be generated from a 304.
170+
repo.getRef(testRefName);
171+
}
172+
173+
private static int clientCount = 0;
174+
175+
private OkHttpClient createClient(boolean useCache) throws IOException {
176+
OkHttpClient client = new OkHttpClient();
177+
178+
if (useCache) {
179+
File cacheDir = new File("target/cache/" + baseFilesClassPath + "/" + mockGitHub.getMethodName()
180+
+ clientCount++);
181+
cacheDir.mkdirs();
182+
FileUtils.cleanDirectory(cacheDir);
183+
Cache cache = new Cache(cacheDir, 100 * 1024L * 1024L);
184+
185+
client.setCache(cache);
186+
}
187+
188+
return client;
189+
}
190+
191+
private static GHRepository getRepository(GitHub gitHub) throws IOException {
192+
return gitHub.getOrganization("github-api-test-org").getRepository("github-api");
193+
}
194+
195+
}

src/test/java/org/kohsuke/github/extras/okhttp3/GitHubCachingTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public GitHubCachingTest() {
4040
@Override
4141
protected WireMockConfiguration getWireMockOptions() {
4242
return super.getWireMockOptions()
43+
// Use the same data files as the 2.x test
44+
.usingFilesUnderDirectory(baseRecordPath.replace("/okhttp3/", "/"))
4345
.extensions(ResponseTemplateTransformer.builder().global(true).maxCacheEntries(0L).build());
4446
}
4547

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/orgs_github-api-test-org-18671a38-8d77-4242-9519-3503350cf496.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/orgs_github-api-test-org-18671a38-8d77-4242-9519-3503350cf496.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api-dac6a9c8-632c-4fe0-8f83-a79f1c361f46.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api-dac6a9c8-632c-4fe0-8f83-a79f1c361f46.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs-ae6bc9aa-4a6b-4022-87ea-c545ee0c7c0a.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs-ae6bc9aa-4a6b-4022-87ea-c545ee0c7c0a.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs-d5310eac-a58b-496e-9829-5bfb59231e49.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs-d5310eac-a58b-496e-9829-5bfb59231e49.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs_heads_test_content_ref_cache-3964781d-6074-412b-bb7a-f3de1bd9ca9c.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs_heads_test_content_ref_cache-3964781d-6074-412b-bb7a-f3de1bd9ca9c.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs_heads_test_content_ref_cache-709410bf-7c2e-48fa-92a7-f493ffbe7262.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs_heads_test_content_ref_cache-709410bf-7c2e-48fa-92a7-f493ffbe7262.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs_heads_test_unmergeable-d47b333a-351b-499c-866b-11122bd52803.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/repos_github-api-test-org_github-api_git_refs_heads_test_unmergeable-d47b333a-351b-499c-866b-11122bd52803.json

File renamed without changes.

src/test/resources/org/kohsuke/github/extras/okhttp3/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/user-0e4fff19-acf2-4b5d-863e-ac84adf7a090.json renamed to src/test/resources/org/kohsuke/github/extras/GitHubCachingTest/wiremock/OkHttpConnector_Cache_MaxAgeDefault_Zero_GitHubRef_Error/__files/user-0e4fff19-acf2-4b5d-863e-ac84adf7a090.json

File renamed without changes.

0 commit comments

Comments
 (0)